該文章閱讀的AFNetworking的版本爲3.2.0。html
AFHTTPRequestSerializer
這個類是用來構建NSMutableURLRequest
,主要作了請求數據序列化,也就是利用傳遞來的HTTP請求方法(如:GET)method
、請求URLURLString
和請求參數parameters
來實例化NSMutableURLRequest
類的對象request
git
FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string);
複製代碼
NSString * AFPercentEscapedStringFromString(NSString *string) {
// 在RFC3986的第3.4節中指出,在對查詢字段百分號編碼時,保留字符中的「?」和「/」能夠不用編碼,其餘的都要進行編碼。
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
// 獲取URL查詢字段容許字符,並從中刪除除「?」和「/」以外的保留字符
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
// return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
// 每50個字符一組進行百分號編碼
static NSUInteger const batchSize = 50;
NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;
while (index < string.length) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wgnu"
NSUInteger length = MIN(string.length - index, batchSize);
#pragma GCC diagnostic pop
NSRange range = NSMakeRange(index, length);
// 每個中文或者英文在NSString中的length均爲1,可是一個Emoji的length的長度爲2或者4,這是爲了不截斷Emoji表情產生亂碼
// To avoid breaking up character sequences such as 👴🏻👮🏽
range = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
return escaped;
}
複製代碼
FOUNDATION_EXPORT NSString * AFQueryStringFromParameters(NSDictionary *parameters);
複製代碼
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
// 把傳入的字典轉成元素爲AFQueryStringPair對象的數組,而後遍歷數組將AFQueryStringPair對象轉成通過百分號編碼的「key=value」類型NSString對象,最後用「&」拼接成一個字符串
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
複製代碼
以上方法調用瞭如下方法並把parameters
做爲參數傳遞過去github
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
// 第一個參數key傳了nil,第二個參數value傳了以上方法傳過來的字典
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
複製代碼
下面這個方法就是對字典進行處理,轉成元素爲AFQueryStringPair
對象的數組json
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
// 設置排序描述爲按照對象的description屬性的字母升序排列
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
// 若是參數value傳入的是NSDictionary
if ([value isKindOfClass:[NSDictionary class]]) {
// 聲明變量保存傳入的字典
NSDictionary *dictionary = value;
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
// 將字典的key按照首字母升序排列後進行遍歷
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
// 若是遍歷出的key所對應的value不爲空,就遞歸調用本方法,若是有key值則傳(key[nestedKey], nestedValue),不然傳(nestedKey, nestedValue)
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
// 若是參數value傳入的是NSArray
} else if ([value isKindOfClass:[NSArray class]]) {
// 聲明變量保存傳入的數組
NSArray *array = value;
// 遍歷數組
for (id nestedValue in array) {
// 遞歸調用本方法,若是有key值則傳遞(key[], nestedValue),不然傳((null)[], nestedValue)
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
// 若是參數value傳入的是NSSet
} else if ([value isKindOfClass:[NSSet class]]) {
// 聲明變量保存傳入的集合
NSSet *set = value;
// 將集合的元素按照首字母升序排列後進行遍歷
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
// 遞歸調用本方法,若是有key值則傳(key, obj),不然傳((null), obj)
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
// 若是參數value傳入的不是集合對象
} else {
// 利用傳入的參數key和value實例化AFQueryStringPair對象並添加到mutableQueryStringComponents數組中
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
// 返回由字典對象轉化元素爲AFQueryStringPair對象組成的數組
return mutableQueryStringComponents;
}
複製代碼
這個協議定義了一個方法,用來將參parameters
數拼接到NSURLRequest
對象中。其中類AFHTTPRequestSerializer
、AFJSONRequestSerializer
和AFPropertyListRequestSerializer
都遵照這個協議數組
@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end
複製代碼
這個協議定義了一系列方法用於在- multipartFormRequestWithMethod:parameters:constructingBodyWithBlock:error:
方法中的代碼塊中爲formData
添加數據緩存
/**
將指定路徑下數據添加到表單中
*/
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * _Nullable __autoreleasing *)error;
/**
將指定路徑下數據添加到表單中,並指定文件類型
*/
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
error:(NSError * _Nullable __autoreleasing *)error;
/**
將指定輸入流中的數據添加到表單中
*/
- (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
name:(NSString *)name
fileName:(NSString *)fileName
length:(int64_t)length
mimeType:(NSString *)mimeType;
/**
將指定NSData對象添加到表單中,並指定文件類型
*/
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType;
/**
將指定NSData對象添加到表單中
*/
- (void)appendPartWithFormData:(NSData *)data
name:(NSString *)name;
/**
將指定的請求頭和請求體添加到表單中
*/
- (void)appendPartWithHeaders:(nullable NSDictionary <NSString *, NSString *> *)headers
body:(NSData *)body;
/**
經過設置請求的帶寬和延遲時間來提升在弱網環境下上傳數據的成功率
*/
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
delay:(NSTimeInterval)delay;
複製代碼
緊接着能夠看到一個枚舉,定義了請求查詢字段的編碼方式,不過目前只定義了一種默認方式bash
typedef NS_ENUM(NSUInteger, AFHTTPRequestQueryStringSerializationStyle) {
AFHTTPRequestQueryStringDefaultStyle = 0,
};
複製代碼
能夠在.h
文件中看到一共有三個類,分別是AFHTTPRequestSerializer
和它的兩個子類AFJSONRequestSerializer
、AFPropertyListRequestSerializer
,先來看一下AFHTTPRequestSerializer
這個類服務器
先在.h
文件中看一下對外暴漏的接口部分cookie
/**
字符串編碼方式,默認爲NSUTF8StringEncoding
*/
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
是否容許使用蜂窩網,默認爲是
*/
@property (nonatomic, assign) BOOL allowsCellularAccess;
/**
請求的緩存策略
*/
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
/**
是否將cookies添加到request的header中一同發送給服務器,默認爲是
*/
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
/**
是否使用管線化,便是否要等到收到前一個請求的響應後才能發送後一個請求,管線化能夠一個發送一組請求,沒必要等待,默認爲否
*/
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
/**
網絡服務類型,系統會根據設置的類型自動優化
*/
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
/**
超時時長,默認爲60秒
*/
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
/**
請求頭信息
*/
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
複製代碼
/**
實例化默認設置對象的方法
*/
+ (instancetype)serializer;
/**
設置請求頭的字段和值,若是值爲nil就移除該字段
*/
- (void)setValue:(nullable NSString *)value
forHTTPHeaderField:(NSString *)field;
/**
獲取請求頭指定字段的值
*/
- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;
/**
利用帳號和密碼爲請求頭的「Authorization」字段賦值
*/
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
password:(NSString *)password;
/**
從請求頭中清除「Authorization」字段的值
*/
- (void)clearAuthorizationHeader;
/**
要把查詢字符串編碼拼接到URL後面的HTTP請求方法集合,默認爲GET、HEAD和DELETE
*/
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
/**
設置查詢字符串的編碼方法,目前AFNetworking只實現了一種,即百分號編碼
*/
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;
/**
設置自定義的查詢字符串編碼方法,只須要在block中實現編碼便可
*/
- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;
/**
利用傳入的HTTP請求方法、請求URL和請求參數三個參數生成NSMutableURLRequest對象。當HTTP請求方法爲GET、HEAD或DELETE時,參數會拼接到URL後面,不然,就添加到請求體中
*/
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error;
/**
利用傳入的HTTP請求方法、請求URL和請求參數三個參數生成multipart/form-dat請求的NSMutableURLRequest對象。
*/
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable NSDictionary <NSString *, id> *)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
error:(NSError * _Nullable __autoreleasing *)error;
/**
移除掉原request中的HTTPBodyStream,並異步寫到指定路徑下,並返回NSMutableURLRequest對象
*/
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
writingStreamContentsToFile:(NSURL *)fileURL
completionHandler:(nullable void (^)(NSError * _Nullable error))handler;
@end
複製代碼
看完了接口部分,再進入.m
文件中看一下私有實現網絡
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)),
NSStringFromSelector(@selector(cachePolicy)),
NSStringFromSelector(@selector(HTTPShouldHandleCookies)),
NSStringFromSelector(@selector(HTTPShouldUsePipelining)),
NSStringFromSelector(@selector(networkServiceType)),
NSStringFromSelector(@selector(timeoutInterval))];
});
return _AFHTTPRequestSerializerObservedKeyPaths;
}
複製代碼
該方法經過一個單例模式獲取須要觀察的AFHTTPRequestSerializer
對象的屬性,並保存在一個數組中返回。
static void *AFHTTPRequestSerializerObserverContext = &AFHTTPRequestSerializerObserverContext;
複製代碼
該變量用於識別觀察者的身份
@interface AFHTTPRequestSerializer ()
// 用來保存須要觀察的用戶自定義的AFHTTPRequestSerializer對象的屬性
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
// 用來保存請求頭信息
@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;
// 用來保存查詢字段編碼類型
@property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle;
// 用來保存用戶自定義的查詢字段編碼方式代碼塊
@property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization;
@end
複製代碼
+ (instancetype)serializer {
// 就是正常的實例化方法
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 初始化字符串編碼方式爲NSUTF8StringEncoding
self.stringEncoding = NSUTF8StringEncoding;
// 初始化請求頭
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
// 獲取前五個用戶偏好的語言並賦值給請求頭Accept-Language字段
NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
[[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
float q = 1.0f - (idx * 0.1f);
[acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
*stop = q <= 0.5f;
}];
[self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];
// 獲取項目名稱(若是沒有則獲取BundleID)、應用Version版本號(若是沒有則獲取應用Build版本號)、設備類型、系統版本號和屏幕縮放比並賦值給請求頭User-Agent字段
NSString *userAgent = nil;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#if TARGET_OS_IOS
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif TARGET_OS_WATCH
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
#pragma clang diagnostic pop
if (userAgent) {
// 若是不能進行無損ASCII編碼,即不是隻有普通的字符或ASCII碼
if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
NSMutableString *mutableUserAgent = [userAgent mutableCopy];
// 若是移除全部非ASCII值範圍的全部字符,移除後再次賦值
if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
userAgent = mutableUserAgent;
}
}
[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
}
// HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
// 初始化須要把查詢字符串編碼拼接到URL後面的HTTP請求方法集合爲GET、HEAD和DELETE方法
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
// 初始化要觀察的自定義的AFHTTPRequestSerializer屬性集合
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
// 遍歷AFHTTPRequestSerializer須要添加觀察的屬性,添加觀察者,並設置上下文爲AFHTTPRequestSerializerObserverContext用於標識
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
return self;
}
- (void)dealloc {
// 遍歷AFHTTPRequestSerializer須要添加觀察的屬性,移除觀察者
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self removeObserver:self forKeyPath:keyPath context:AFHTTPRequestSerializerObserverContext];
}
}
}
複製代碼
- (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
[self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
_allowsCellularAccess = allowsCellularAccess;
[self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
}
- (void)setCachePolicy:(NSURLRequestCachePolicy)cachePolicy {
[self willChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
_cachePolicy = cachePolicy;
[self didChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
}
- (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies {
[self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
_HTTPShouldHandleCookies = HTTPShouldHandleCookies;
[self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
}
- (void)setHTTPShouldUsePipelining:(BOOL)HTTPShouldUsePipelining {
[self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))];
_HTTPShouldUsePipelining = HTTPShouldUsePipelining;
[self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))];
}
- (void)setNetworkServiceType:(NSURLRequestNetworkServiceType)networkServiceType {
[self willChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))];
_networkServiceType = networkServiceType;
[self didChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))];
}
- (void)setTimeoutInterval:(NSTimeInterval)timeoutInterval {
[self willChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))];
_timeoutInterval = timeoutInterval;
[self didChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))];
}
複製代碼
- (NSDictionary *)HTTPRequestHeaders {
// 返回私有屬性mutableHTTPRequestHeaders
return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
}
- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
// 爲私有屬性mutableHTTPRequestHeaders賦值
[self.mutableHTTPRequestHeaders setValue:value forKey:field];
}
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
// 獲取私有屬性mutableHTTPRequestHeaders指定key的值
return [self.mutableHTTPRequestHeaders valueForKey:field];
}
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
password:(NSString *)password
{
// 先把帳戶和密碼拼接成一個字符串後轉爲UTF8格式的NSData對象,再經過base64編碼成字符串賦值給請求頭的Authorization字段
NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
[self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
}
- (void)clearAuthorizationHeader {
// 從請求頭中移除Authorization字段
[self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
}
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style {
// 若是設置了編碼格式就把自定義編碼代碼塊置nil
self.queryStringSerializationStyle = style;
self.queryStringSerialization = nil;
}
- (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block {
// 這是爲了用戶在設置代碼塊時有智能提示,能夠直接回車敲出
self.queryStringSerialization = block;
}
複製代碼
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
// 在debug模式下缺乏對應參數會crash
NSParameterAssert(method);
NSParameterAssert(URLString);
// 利用傳入的路徑生成NSURL對象
NSURL *url = [NSURL URLWithString:URLString];
// 判斷url是否生成
NSParameterAssert(url);
// 利用生成的NSURL對象生成NSMutableURLRequest對象,並設置請求方式
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
// 遍歷AFHTTPRequestSerializer須要添加觀察的屬性
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
// 若是遍歷出的屬性是用戶自定義的屬性
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
// 將屬性對應的值賦值給NSMutableURLRequest對象相對應的屬性
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
// 將傳入的參數parameters處理後添加到mutableRequest中
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
// 返回mutableRequest
return mutableRequest;
}
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
error:(NSError *__autoreleasing *)error
{
// 沒有傳請求方法就crash
NSParameterAssert(method);
// 請求方法是GET或HEAD就crash
NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);
// 調用上個公共方法生成NSMutableURLRequest對象
NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
// 利用NSMutableURLRequest對象生成AFStreamingMultipartFormData對象formData
__block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
// 若是傳遞了參數
if (parameters) {
// 將傳入的字典參數轉爲元素是AFQueryStringPair對象的數組,並進行遍歷
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
// 將對象pair的value屬性轉爲NSData對象,並拼到formData對象中
NSData *data = nil;
if ([pair.value isKindOfClass:[NSData class]]) {
data = pair.value;
} else if ([pair.value isEqual:[NSNull null]]) {
data = [NSData data];
} else {
data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
}
if (data) {
[formData appendPartWithFormData:data name:[pair.field description]];
}
}
}
// 調用代碼塊拼接想要上傳的數據
if (block) {
block(formData);
}
// 構建multipart/form-data請求獨有的請求頭
return [formData requestByFinalizingMultipartFormData];
}
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
writingStreamContentsToFile:(NSURL *)fileURL
completionHandler:(void (^)(NSError *error))handler
{
// request對象的HTTPBodyStream屬性爲nil則crash
NSParameterAssert(request.HTTPBodyStream);
// fileURL不是合法的文件路徑則crash
NSParameterAssert([fileURL isFileURL]);
// 生成輸入流和輸出流
NSInputStream *inputStream = request.HTTPBodyStream;
NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
__block NSError *error = nil;
// 全局併發隊列異步執行寫入操做
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 把輸入輸出流添加到默認模式的當前運行循環中
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
// 打開輸入輸出流
[inputStream open];
[outputStream open];
// 若是輸入輸出流還有可操做字節
while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
uint8_t buffer[1024];
// 每次從輸入流中讀取最大1024bytes大小的數據存入buffer中,若是出錯則跳出循環
NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
if (inputStream.streamError || bytesRead < 0) {
error = inputStream.streamError;
break;
}
// 將從輸入流中讀取出的數據寫入到輸出流中,若是出錯則跳出循環
NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
if (outputStream.streamError || bytesWritten < 0) {
error = outputStream.streamError;
break;
}
// 若是讀寫完則跳出循環
if (bytesRead == 0 && bytesWritten == 0) {
break;
}
}
// 關閉輸入輸出流
[outputStream close];
[inputStream close];
// 若是傳入了回調代碼塊則在主隊列異步回調
if (handler) {
dispatch_async(dispatch_get_main_queue(), ^{
handler(error);
});
}
});
// 把原mutableRequest對象的HTTPBodyStream屬性置nil後返回
NSMutableURLRequest *mutableRequest = [request mutableCopy];
mutableRequest.HTTPBodyStream = nil;
return mutableRequest;
}
複製代碼
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
// 缺乏request則會crash
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
// 遍歷request的請求頭,對沒有值的字段進行賦值
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
// 對參數parameters進行編碼
NSString *query = nil;
if (parameters) {
// 若是用戶自定義了編碼代碼塊則用用戶自定義的方法編碼
if (self.queryStringSerialization) {
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
// 若是用戶沒有自定義編碼代碼塊則用AFNetworking默認的編碼方式,即百分號編碼
} else {
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
// 若是HTTP請求方法爲GET、HEAD或DELETE其中之一
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
// 就把查詢字符串拼接到url後面
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
// 若是HTTP請求方法爲POST、PUT其中之一
} else {
// 就把查詢字符串拼接到請求體中
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
// 返回拼接好參數的mutableRequest對象
return mutableRequest;
}
複製代碼
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
// 若是是須要觀察的AFHTTPRequestSerializer對象的屬性,則不自動實現KVO
if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context
{
// 若是觀察到的是AFHTTPRequestSerializer類添加觀察的屬性
if (context == AFHTTPRequestSerializerObserverContext) {
// 若是給當前屬性賦的值不爲null就添加到self.mutableObservedChangedKeyPaths中,不然從其中移除
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}
複製代碼
在iOS6中,蘋果引入了一個新的協議,是基於NSCoding
的,叫作NSSecureCoding
。NSSecureCoding
和NSCoding
是同樣的,除了在解碼時要同時指定key和要解碼的對象的類,若是要求的類和從文件中解碼出的對象的類不匹配,NSCoder
會拋出異常,告訴你數據已經被篡改了。
+ (BOOL)supportsSecureCoding {
// 若是一個類符合 NSSecureCoding 協議並在 + supportsSecureCoding 返回 YES,就聲明瞭它能夠處理自己實例的編碼解碼方式,以防止替換攻擊。
return YES;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [self init];
if (!self) {
return nil;
}
self.mutableHTTPRequestHeaders = [[decoder decodeObjectOfClass:[NSDictionary class] forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))] mutableCopy];
self.queryStringSerializationStyle = (AFHTTPRequestQueryStringSerializationStyle)[[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))] unsignedIntegerValue];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.mutableHTTPRequestHeaders forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))];
[coder encodeInteger:self.queryStringSerializationStyle forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))];
}
複製代碼
- (instancetype)copyWithZone:(NSZone *)zone {
AFHTTPRequestSerializer *serializer = [[[self class] allocWithZone:zone] init];
serializer.mutableHTTPRequestHeaders = [self.mutableHTTPRequestHeaders mutableCopyWithZone:zone];
serializer.queryStringSerializationStyle = self.queryStringSerializationStyle;
serializer.queryStringSerialization = self.queryStringSerialization;
return serializer;
}
@end
複製代碼
AFJSONRequestSerializer
是AFHTTPRequestSerializer
的子類,當服務器要求咱們上傳的數據格式類型爲json時,就可使用此類
// 設置JSON的編碼類型
@property (nonatomic, assign) NSJSONWritingOptions writingOptions;
複製代碼
// 實例化工廠方法
+ (instancetype)serializerWithWritingOptions:(NSJSONWritingOptions)writingOptions;
複製代碼
+ (instancetype)serializer {
// 調用下面的方法並傳默認的JSON輸出格式
return [self serializerWithWritingOptions:(NSJSONWritingOptions)0];
}
+ (instancetype)serializerWithWritingOptions:(NSJSONWritingOptions)writingOptions
{
// 調用父類的初始化方法並保存了傳入的參數
AFJSONRequestSerializer *serializer = [[self alloc] init];
serializer.writingOptions = writingOptions;
return serializer;
}
複製代碼
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
// 缺乏request則會crash
NSParameterAssert(request);
// 若是HTTP請求方法爲GET、HEAD或DELETE其中之一
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
// 就直接調用父類的實現並返回
return [super requestBySerializingRequest:request withParameters:parameters error:error];
}
NSMutableURLRequest *mutableRequest = [request mutableCopy];
// 遍歷request的請求頭,對沒有值的字段進行賦值
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
// 若是傳入了參數
if (parameters) {
// 若是mutableRequest的請求頭的Content-Type字段沒有值
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
// 爲mutableRequest的請求頭的Content-Type字段賦值爲application/json
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
}
// 將傳入的parameters轉成JSON格式的NSData對象並添加到mutableRequest的請求體中
[mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]];
}
return mutableRequest;
}
複製代碼
就是在父類的基礎上添加了writingOptions屬性
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (!self) {
return nil;
}
self.writingOptions = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(writingOptions))] unsignedIntegerValue];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
[coder encodeInteger:self.writingOptions forKey:NSStringFromSelector(@selector(writingOptions))];
}
複製代碼
一樣也是在父類的基礎上添加了writingOptions屬性
- (instancetype)copyWithZone:(NSZone *)zone {
AFJSONRequestSerializer *serializer = [super copyWithZone:zone];
serializer.writingOptions = self.writingOptions;
return serializer;
}
複製代碼
AFPropertyListRequestSerializer
是AFHTTPRequestSerializer
的子類,此類能夠把傳入的參數編碼成plist格式NSDate對象傳給服務器,通常是用來向服務器傳遞XML格式的數據
// plist輸出格式
@property (nonatomic, assign) NSPropertyListFormat format;
// plist編碼類型,目前這個值尚未用
@property (nonatomic, assign) NSPropertyListWriteOptions writeOptions;
複製代碼
// 實例化工廠方法
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
writeOptions:(NSPropertyListWriteOptions)writeOptions;tions;
複製代碼
+ (instancetype)serializer {
// 調用下面的實例化方法,設置plist的輸出格式爲XML類型
return [self serializerWithFormat:NSPropertyListXMLFormat_v1_0 writeOptions:0];
}
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
writeOptions:(NSPropertyListWriteOptions)writeOptions
{
// 調用父類的初始化方法並保存了傳入的參數
AFPropertyListRequestSerializer *serializer = [[self alloc] init];
serializer.format = format;
serializer.writeOptions = writeOptions;
return serializer;
}
複製代碼
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
// 缺乏request則會crash
NSParameterAssert(request);
// 若是HTTP請求方法爲GET、HEAD或DELETE其中之一
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
// 就直接調用父類的實現並返回
return [super requestBySerializingRequest:request withParameters:parameters error:error];
}
NSMutableURLRequest *mutableRequest = [request mutableCopy];
// 遍歷request的請求頭,對沒有值的字段進行賦值
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
// 若是傳入了參數
if (parameters) {
// 若是mutableRequest的請求頭的Content-Type字段沒有值
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
// // 爲mutableRequest的請求頭的Content-Type字段賦值application/x-plist
[mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"];
}
// 將傳入的parameters轉成plist格式的NSData對象並添加到mutableRequest的請求體中
[mutableRequest setHTTPBody:[NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error]];
}
return mutableRequest;
}
複製代碼
/**
AFURLRequestSerializer類的錯誤,錯誤碼對應NSURLErrorDomain的錯誤碼
*/
FOUNDATION_EXPORT NSString * const AFURLRequestSerializationErrorDomain;
/**
這個key只存在AFURLRequestSerializationErrorDomain中,其對應的值是NSURLRequest錯誤請求的操做
*/
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLRequestErrorKey;
/**
HTTP請求輸入流的節流帶寬字節數的最大分組大小。等於16KB。
*/
FOUNDATION_EXPORT NSUInteger const kAFUploadStream3GSuggestedPacketSize;
/**
HTTP請求輸入流的節流帶寬每次讀取數據包時的延遲時間。等於0.2秒。
*/
FOUNDATION_EXPORT NSTimeInterval const kAFUploadStream3GSuggestedDelay;
複製代碼
在對請求的查詢參數編碼時,傳入的參數會被拆分紅最小的集合類對象,而後將集合對象轉成AFQueryStringPair
對象,再對field和value百分號編碼後生成field=value
類型的字符串
@property (readwrite, nonatomic, strong) id field; // 字段
@property (readwrite, nonatomic, strong) id value; // 值
複製代碼
/**
AFQueryStringPair對象初始化方法
@param field 字段
@param value 值
@return 初始化的AFQueryStringPair對象
*/
- (instancetype)initWithField:(id)field value:(id)value;
/**
將屬性field和value進行百分號編碼後,之間用」=「拼接成一個字符串
@return 處理好的字符串
*/
- (NSString *)URLEncodedStringValue;
複製代碼
- (instancetype)initWithField:(id)field value:(id)value {
self = [super init];
if (!self) {
return nil;
}
// 屬性保存初始化傳入的參數
self.field = field;
self.value = value;
return self;
}
- (NSString *)URLEncodedStringValue {
// 若是value值爲nil或null
if (!self.value || [self.value isEqual:[NSNull null]]) {
// 只把屬性field的字符串描述屬性進行百分號編碼後返回
return AFPercentEscapedStringFromString([self.field description]);
// 若是value值不爲nil或null
} else {
// 把屬性field和value進行百分號編碼後,之間用」=「拼接成一個字符串返回
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
}
}
複製代碼
每個AFHTTPBodyPart
就是表明一項表單數據,即一個要上傳的文件的數據,並由它本身讀取它內部的數據
/**
回車換行
*/
static NSString * const kAFMultipartFormCRLF = @"\r\n";
/**
3G環境上傳建議帶寬
*/
NSUInteger const kAFUploadStream3GSuggestedPacketSize = 1024 * 16;
/**
3G環境上傳建議延時
*/
NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2;
複製代碼
/**
由隨機生成的八位16進制字符串組成的邊界字符串
*/
static NSString * AFCreateMultipartFormBoundary() {
return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
}
/**
生成開始邊界字符串
*/
static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF];
}
/**
生成中間邊界字符串
*/
static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}
/**
生成結束邊界字符串
*/
static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}
/**
根據文件後綴名獲取文件的MIME類型,即Content-Type字段的值
*/
static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
// 經過傳入的文件後綴字符串生成一個UTI字符串(統一類型標識符是惟一標識抽象類型的字符串。它們能夠用來描述文件格式或內存中的數據類型,但也能夠用來描述其餘類型的實體類型,如目錄,卷或包。)
NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
// 將UTI轉成MIME類型
NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
if (!contentType) {
return @"application/octet-stream";
} else {
return contentType;
}
}
複製代碼
/**
編碼方式
*/
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
段落頭
*/
@property (nonatomic, strong) NSDictionary *headers;
/**
邊界
*/
@property (nonatomic, copy) NSString *boundary;
/**
內容
*/
@property (nonatomic, strong) id body;
/**
內容長度
*/
@property (nonatomic, assign) unsigned long long bodyContentLength;
/**
輸入流
*/
@property (nonatomic, strong) NSInputStream *inputStream;
/**
是否有開始邊界
*/
@property (nonatomic, assign) BOOL hasInitialBoundary;
/**
是否有結束邊界
*/
@property (nonatomic, assign) BOOL hasFinalBoundary;
/**
內容長度
*/
@property (readonly, nonatomic, assign, getter = hasBytesAvailable) BOOL bytesAvailable;
/**
內容長度
*/
@property (readonly, nonatomic, assign) unsigned long long contentLength;
複製代碼
/**
將AFHTTPBodyPart對象中的數據讀出,並寫入到buffer中,也就是AFHTTPBodyPart對象本身把本身保存的數據讀取出來,而後寫入到傳遞進來的參數buffer中
*/
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length;
複製代碼
typedef enum {
AFEncapsulationBoundaryPhase = 1, // 中間邊界段落
AFHeaderPhase = 2, // 頭段落
AFBodyPhase = 3, // 內容段落
AFFinalBoundaryPhase = 4, // 結束邊界段落
} AFHTTPBodyPartReadPhase;
複製代碼
/**
保存要讀取的段落,其實就是利用狀態機模式控制對AFHTTPBodyPart對象不一樣內容的讀取
*/
AFHTTPBodyPartReadPhase _phase;
/**
保存由AFHTTPBodyPart對象的body屬性生成的輸入流對象
*/
NSInputStream *_inputStream;
/**
保存當前已讀取字節數,用來計算讀取進度
*/
unsigned long long _phaseReadOffset;
複製代碼
/**
切換到下一段落進行讀取,即控制狀態機的狀態
*/
- (BOOL)transitionToNextPhase;
/**
將AFHTTPBodyPart對象的屬性中保存的數據轉成的NSDdata對象寫入到buffer中
*/
- (NSInteger)readData:(NSData *)data
intoBuffer:(uint8_t *)buffer
maxLength:(NSUInteger)length;
複製代碼
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 切換到主線程,初始化成員變量_phase爲AFEncapsulationBoundaryPhase,_phaseReadOffset爲0
[self transitionToNextPhase];
return self;
}
- (void)dealloc {
// 關閉輸入流並置空
if (_inputStream) {
[_inputStream close];
_inputStream = nil;
}
}
/**
inputStream的懶加載方法
*/
- (NSInputStream *)inputStream {
if (!_inputStream) {
// 根據body屬性的類生成對應的NSInputStream對象並保存
if ([self.body isKindOfClass:[NSData class]]) {
_inputStream = [NSInputStream inputStreamWithData:self.body];
} else if ([self.body isKindOfClass:[NSURL class]]) {
_inputStream = [NSInputStream inputStreamWithURL:self.body];
} else if ([self.body isKindOfClass:[NSInputStream class]]) {
_inputStream = self.body;
} else {
_inputStream = [NSInputStream inputStreamWithData:[NSData data]];
}
}
return _inputStream;
}
/**
將headers屬性所保存的字典類型的數據拼接成指定格式的字符串
*/
- (NSString *)stringForHeaders {
NSMutableString *headerString = [NSMutableString string];
for (NSString *field in [self.headers allKeys]) {
[headerString appendString:[NSString stringWithFormat:@"%@: %@%@", field, [self.headers valueForKey:field], kAFMultipartFormCRLF]];
}
[headerString appendString:kAFMultipartFormCRLF];
return [NSString stringWithString:headerString];
}
/**
獲取內容的總長度
*/
- (unsigned long long)contentLength {
unsigned long long length = 0;
// 若是有開始邊界就生成開始邊界字符串,不然就生成中間邊界字符串,而後生成對應的NSData對象,並獲取長度
NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding];
length += [encapsulationBoundaryData length];
// 添加header對應的NSData對象的長度
NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
length += [headersData length];
// 添加body對應的NSData對象的長度
length += _bodyContentLength;
// 若是有結束邊界就生成結束邊界字符串,不然就生成中間邊界字符串,而後生成對應的NSData對象,並獲取長度後添加
NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]);
length += [closingBoundaryData length];
return length;
}
/**
判斷是否有可讀數據
*/
- (BOOL)hasBytesAvailable {
// Allows `read:maxLength:` to be called again if `AFMultipartFormFinalBoundary` does not fit into the available buffer
if (_phase == AFFinalBoundaryPhase) {
return YES;
}
// 根據inputStream的屬性streamStatus來判斷是否有可讀數據
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcovered-switch-default"
switch (self.inputStream.streamStatus) {
case NSStreamStatusNotOpen:
case NSStreamStatusOpening:
case NSStreamStatusOpen:
case NSStreamStatusReading:
case NSStreamStatusWriting:
return YES;
case NSStreamStatusAtEnd:
case NSStreamStatusClosed:
case NSStreamStatusError:
default:
return NO;
}
#pragma clang diagnostic pop
}
/**
將自身的數據寫入到buffer中
*/
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
NSInteger totalNumberOfBytesRead = 0;
// 若是要讀取的段落是中間邊界段落
if (_phase == AFEncapsulationBoundaryPhase) {
// 根據是否有開始邊界生成對應的邊界字符串,而後生成相應的NSData對象,寫入到butter中
NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding];
totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
}
// 若是要讀取的段落是頭部段落
if (_phase == AFHeaderPhase) {
// 將header編碼寫入到buffer中
NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
}
// 若是要讀取的段落是內容段落
if (_phase == AFBodyPhase) {
// 將屬性body中保存的數據轉爲NSInputStream對象再寫入到buffer中
NSInteger numberOfBytesRead = 0;
numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
if (numberOfBytesRead == -1) {
return -1;
} else {
totalNumberOfBytesRead += numberOfBytesRead;
// 若是inputStream的狀態是結束、關閉或者出錯,就切換狀態機的狀態
if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) {
[self transitionToNextPhase];
}
}
}
// 若是要讀取的段落是結束邊界段落
if (_phase == AFFinalBoundaryPhase) {
// 根據是否有結束邊界生成對應的邊界字符串,而後生成相應的NSData對象,寫入到butter中
NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]);
totalNumberOfBytesRead += [self readData:closingBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
}
return totalNumberOfBytesRead;
}
/**
將data中的數據寫入到buffer中
*/
- (NSInteger)readData:(NSData *)data
intoBuffer:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
// 計算要讀取的範圍
NSRange range = NSMakeRange((NSUInteger)_phaseReadOffset, MIN([data length] - ((NSUInteger)_phaseReadOffset), length));
// 根據計算好的範圍讀寫
[data getBytes:buffer range:range];
#pragma clang diagnostic pop
// 記錄讀寫的進度
_phaseReadOffset += range.length;
// 若是data中的數據讀寫完成,就切換狀態機的狀態
if (((NSUInteger)_phaseReadOffset) >= [data length]) {
[self transitionToNextPhase];
}
return (NSInteger)range.length;
}
/**
切換到下一段落進行讀取,即控制狀態機的狀態
*/
- (BOOL)transitionToNextPhase {
// 若是該方法不是在主線程調用,就切換到主線程
if (![[NSThread currentThread] isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self transitionToNextPhase];
});
return YES;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcovered-switch-default"
// 根據目前正在讀取的段落,修改接下來要讀取的段落
switch (_phase) {
// 若是如今讀取的是中間邊界段落,接下來就要讀取頭部段落
case AFEncapsulationBoundaryPhase:
_phase = AFHeaderPhase;
break;
// 若是如今讀取的是頭部段落,接下來就要讀取內容段落,初始化inputStream添加到當前運行循環中,並開啓
case AFHeaderPhase:
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.inputStream open];
_phase = AFBodyPhase;
break;
// 若是如今讀取的是內容段落,接下來就要讀取結束邊界段落,關閉inputStream
case AFBodyPhase:
[self.inputStream close];
_phase = AFFinalBoundaryPhase;
break;
// 若是如今讀取的是結束邊界段落,就賦值爲中間邊界段落
case AFFinalBoundaryPhase:
default:
_phase = AFEncapsulationBoundaryPhase;
break;
}
// 段落讀取偏移量置零
_phaseReadOffset = 0;
#pragma clang diagnostic pop
return YES;
}
複製代碼
- (instancetype)copyWithZone:(NSZone *)zone {
AFHTTPBodyPart *bodyPart = [[[self class] allocWithZone:zone] init];
// 複製了主要屬性
bodyPart.stringEncoding = self.stringEncoding;
bodyPart.headers = self.headers;
bodyPart.bodyContentLength = self.bodyContentLength;
bodyPart.body = self.body;
bodyPart.boundary = self.boundary;
return bodyPart;
}
複製代碼
AFMultipartBodyStream
類繼承自NSInputStream
類,並遵照了NSStreamDelegate
協議。這個類保存着用戶要上傳的數據,並在數據上傳時控制數據的讀取。
/**
單個包的大小
*/
@property (nonatomic, assign) NSUInteger numberOfBytesInPacket;
/**
延時
*/
@property (nonatomic, assign) NSTimeInterval delay;
/**
輸入流
*/
@property (nonatomic, strong) NSInputStream *inputStream;
/**
內容大小
*/
@property (readonly, nonatomic, assign) unsigned long long contentLength;
/**
是否爲空
*/
@property (readonly, nonatomic, assign, getter = isEmpty) BOOL empty;
複製代碼
/**
經過編碼方式初始化
*/
- (instancetype)initWithStringEncoding:(NSStringEncoding)encoding;
/**
設置開始和結束邊界
*/
- (void)setInitialAndFinalBoundaries;
/**
添加AFHTTPBodyPart對象
*/
- (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart;
複製代碼
由於AFMultipartBodyStream
類繼承自NSInputStream
類,而NSInputStream
繼承自NSStream
類,但NSStream
類的streamStatus
屬性和streamError
屬性是readonly
,想要在AFMultipartBodyStream
類內部使用讀寫這兩個屬性,因而添加了類擴展,改成私有可讀寫的。
@property (readwrite) NSStreamStatus streamStatus;
@property (readwrite, copy) NSError *streamError;
複製代碼
但這樣會出現一個問題:本來只要經過@property
聲明屬性,編譯器就會自動幫咱們生成getter
、setter
和成員變量,可是子類經過@property
覆蓋了父類的屬性,這時編譯器就不會自動生成成員變量,所以在AFMultipartBodyStream
類的@implementation
中能夠看到@synthesize streamStatus;
和@synthesize streamError;
兩句代碼來生成成員變量;
/**
編碼方式
*/
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;
/**
保存AFHTTPBodyPart的數組
*/
@property (readwrite, nonatomic, strong) NSMutableArray *HTTPBodyParts;
/**
保存對屬性HTTPBodyParts內容的遍歷
*/
@property (readwrite, nonatomic, strong) NSEnumerator *HTTPBodyPartEnumerator;
/**
當前讀寫的HTTPBodyPart
*/
@property (readwrite, nonatomic, strong) AFHTTPBodyPart *currentHTTPBodyPart;
/**
輸出流
*/
@property (readwrite, nonatomic, strong) NSOutputStream *outputStream;
/**
緩衝
*/
@property (readwrite, nonatomic, strong) NSMutableData *buffer;
複製代碼
// 這三個屬性在6.3.2已經解釋了
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wimplicit-atomic-properties"
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1100)
@synthesize delegate;
#endif
@synthesize streamStatus;
@synthesize streamError;
#pragma clang diagnostic pop
- (instancetype)initWithStringEncoding:(NSStringEncoding)encoding {
self = [super init];
if (!self) {
return nil;
}
// 保存傳入的參數和初始化屬性
self.stringEncoding = encoding;
self.HTTPBodyParts = [NSMutableArray array];
self.numberOfBytesInPacket = NSIntegerMax;
return self;
}
- (void)setInitialAndFinalBoundaries {
// 若是屬性HTTPBodyParts內有元素,就將第一個元素設置爲有開始邊界,最後一個元素設置爲有結束邊界,其餘元素都設置爲無
if ([self.HTTPBodyParts count] > 0) {
for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) {
bodyPart.hasInitialBoundary = NO;
bodyPart.hasFinalBoundary = NO;
}
[[self.HTTPBodyParts firstObject] setHasInitialBoundary:YES];
[[self.HTTPBodyParts lastObject] setHasFinalBoundary:YES];
}
}
- (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart {
// 向HTTPBodyParts屬性內添加元素
[self.HTTPBodyParts addObject:bodyPart];
}
- (BOOL)isEmpty {
// 判斷HTTPBodyParts屬性內是否有元素
return [self.HTTPBodyParts count] == 0;
}
複製代碼
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
// 若是輸入流的狀態是關閉就結束
if ([self streamStatus] == NSStreamStatusClosed) {
return 0;
}
// 定義變量記錄已讀取總數
NSInteger totalNumberOfBytesRead = 0;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
// 只要已讀取的數量小於限定的數量和包的總數量兩者中的最小值
while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
// 若是當前HTTPBodyPart爲空或者沒有可讀數據
if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
// 爲currentHTTPBodyPart賦值,但若是下一個元素爲空則跳出循環
if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
break;
}
// 若是當前HTTPBodyPart有值
} else {
// 計算還能讀取的最大數量
NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
// 將currentHTTPBodyPart中的數據寫入到buffer中
NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
// 若是寫入失敗
if (numberOfBytesRead == -1) {
// 記錄錯誤並跳出循環
self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
break;
} else {
// 記錄當前已讀總數
totalNumberOfBytesRead += numberOfBytesRead;
// 若是設置了延時,就在當前線程延時一段時間
if (self.delay > 0.0f) {
[NSThread sleepForTimeInterval:self.delay];
}
}
}
}
#pragma clang diagnostic pop
return totalNumberOfBytesRead;
}
- (BOOL)getBuffer:(__unused uint8_t **)buffer
length:(__unused NSUInteger *)len
{
// 關閉讀取緩存的方法
return NO;
}
- (BOOL)hasBytesAvailable {
// 只要狀態爲開就是有數據
return [self streamStatus] == NSStreamStatusOpen;
}
複製代碼
- (void)open {
// 若是流的狀態是打開就不繼續執行
if (self.streamStatus == NSStreamStatusOpen) {
return;
}
// 將流的狀態設置爲打開
self.streamStatus = NSStreamStatusOpen;
// 設置開始和結束邊界
[self setInitialAndFinalBoundaries];
// 初始化HTTPBodyPartEnumerator屬性
self.HTTPBodyPartEnumerator = [self.HTTPBodyParts objectEnumerator];
}
- (void)close {
// 將流的狀態設置爲關閉
self.streamStatus = NSStreamStatusClosed;
}
- (id)propertyForKey:(__unused NSString *)key {
// 關閉對key屬性的查詢
return nil;
}
- (BOOL)setProperty:(__unused id)property
forKey:(__unused NSString *)key
{
// 關閉對key屬性的賦值
return NO;
}
// 將設置和移除運行環境的方法設置爲何都不作
- (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{}
- (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{}
- (unsigned long long)contentLength {
// 遍歷HTTPBodyParts中的元素計算總長度
unsigned long long length = 0;
for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) {
length += [bodyPart contentLength];
}
return length;
}
複製代碼
爲何要重寫私有方法?由於NSMutableURLRequest
的setHTTPBodyStream
方法接受的是一個NSInputStream *
參數,那咱們要自定義NSInputStream
的話,建立一個NSInputStream
的子類傳給它是否是就能夠了?實際上不行,這樣作後用NSMutableURLRequest
發出請求會致使crash,提示[xx _scheduleInCFRunLoop:forMode:]: unrecognized selector
。
這是由於NSMutableURLRequest
實際上接受的不是NSInputStream
對象,而是CoreFoundation
的CFReadStreamRef
對象,因爲CFReadStreamRef
和NSInputStream
是toll-free bridged
,能夠自由轉換,但CFReadStreamRef
會用到CFStreamScheduleWithRunLoop
這個方法,當它調用到這個方法時,object-c
的toll-free bridging
機制會調用object-c
對象NSInputStream
的相應函數,這裏就調用到了_scheduleInCFRunLoop:forMode:
,若不實現這個方法就會crash。以上解釋摘自這篇博客
- (void)_scheduleInCFRunLoop:(__unused CFRunLoopRef)aRunLoop
forMode:(__unused CFStringRef)aMode
{}
- (void)_unscheduleFromCFRunLoop:(__unused CFRunLoopRef)aRunLoop
forMode:(__unused CFStringRef)aMode
{}
- (BOOL)_setCFClientFlags:(__unused CFOptionFlags)inFlags
callback:(__unused CFReadStreamClientCallBack)inCallback
context:(__unused CFStreamClientContext *)inContext {
return NO;
}
複製代碼
- (instancetype)copyWithZone:(NSZone *)zone {
// 拷貝了HTTPBodyParts並設置了啓示和結束邊界
AFMultipartBodyStream *bodyStreamCopy = [[[self class] allocWithZone:zone] initWithStringEncoding:self.stringEncoding];
for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) {
[bodyStreamCopy appendHTTPBodyPart:[bodyPart copy]];
}
[bodyStreamCopy setInitialAndFinalBoundaries];
return bodyStreamCopy;
}
複製代碼
這個類的做用是提供接口以便用戶添加上傳的數據。
當用戶添加數據時,該類會將用戶想要上傳的數據分別轉成AFHTTPBodyPart
對象,而後依次保存到自身AFMultipartBodyStream *
類型的屬性bodyStream
中,當數據添加完成,就會將屬性bodyStream
賦值給NSMutableURLRequest
的HTTPBodyStream
屬性。
/**
經過傳遞請求和編碼方式進行初始化
*/
- (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest
stringEncoding:(NSStringEncoding)encoding;
/**
返回最終處理好的NSMutableURLRequest
*/
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData;
複製代碼
/**
保存傳入的NSMutableURLRequest對象
*/
@property (readwrite, nonatomic, copy) NSMutableURLRequest *request;
/**
保存傳入的編碼方式
*/
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;
/**
保存邊界字符串
*/
@property (readwrite, nonatomic, copy) NSString *boundary;
/**
保存輸入數據流
*/
@property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream;
複製代碼
- (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest
stringEncoding:(NSStringEncoding)encoding
{
self = [super init];
if (!self) {
return nil;
}
// 保存傳入的參數,初始化私有屬性
self.request = urlRequest;
self.stringEncoding = encoding;
self.boundary = AFCreateMultipartFormBoundary();
self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding];
return self;
}
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
// 若是沒有數據流就直接返回NSMutableURLRequest對象
if ([self.bodyStream isEmpty]) {
return self.request;
}
// 設置數據流的開始和結束邊界
// Reset the initial and final boundaries to ensure correct Content-Length
[self.bodyStream setInitialAndFinalBoundaries];
// 將數據流賦值給NSMutableURLRequest對象
[self.request setHTTPBodyStream:self.bodyStream];
// 爲NSMutableURLRequest對象的請求頭的Content-Type和Content-Length字段賦值
[self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
[self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"];
return self.request;
}
複製代碼
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * __autoreleasing *)error
{
// 在debug模式下缺乏對應參數會crash
NSParameterAssert(fileURL);
NSParameterAssert(name);
// 經過文件的路徑中獲取帶有後綴的文件名
NSString *fileName = [fileURL lastPathComponent];
// 經過文件的路徑獲取不帶「.」的後綴名後獲取文件的mime類型
NSString *mimeType = AFContentTypeForPathExtension([fileURL pathExtension]);
// 調用下面那個方法
return [self appendPartWithFileURL:fileURL name:name fileName:fileName mimeType:mimeType error:error];
}
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
error:(NSError * __autoreleasing *)error
{
// 在debug模式下缺乏對應參數會crash
NSParameterAssert(fileURL);
NSParameterAssert(name);
NSParameterAssert(fileName);
NSParameterAssert(mimeType);
// 若是不是一個合法的文件路徑
if (![fileURL isFileURL]) {
// 就生成一個錯誤信息賦值給傳入的錯誤對象指針後返回
NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"Expected URL to be a file URL", @"AFNetworking", nil)};
if (error) {
*error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo];
}
return NO;
// 若是文件路徑沒法訪問
} else if ([fileURL checkResourceIsReachableAndReturnError:error] == NO) {
// 就生成一個錯誤信息賦值給傳入的錯誤對象指針後返回
NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"File URL not reachable.", @"AFNetworking", nil)};
if (error) {
*error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo];
}
return NO;
}
// 經過文件路徑獲取文件的屬性,若是獲取不到則返回,由於沒法獲取到文件的大小
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error];
if (!fileAttributes) {
return NO;
}
// 生成一個可變字典保存請求頭的相關信息,併爲Content-Disposition和Content-Type字段賦值
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
[mutableHeaders setValue:mimeType forKey:@"Content-Type"];
// 生成一個AFHTTPBodyPart對象保存要傳輸的內容,並添加到私有屬性bodyStream中
AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
bodyPart.stringEncoding = self.stringEncoding;
bodyPart.headers = mutableHeaders;
bodyPart.boundary = self.boundary;
bodyPart.body = fileURL;
bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue];
[self.bodyStream appendHTTPBodyPart:bodyPart];
return YES;
}
- (void)appendPartWithInputStream:(NSInputStream *)inputStream
name:(NSString *)name
fileName:(NSString *)fileName
length:(int64_t)length
mimeType:(NSString *)mimeType
{
// 在debug模式下缺乏對應參數會crash
NSParameterAssert(name);
NSParameterAssert(fileName);
NSParameterAssert(mimeType);
// 生成一個可變字典保存請求頭的相關信息,併爲Content-Disposition和Content-Type字段賦值
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
[mutableHeaders setValue:mimeType forKey:@"Content-Type"];
// 生成一個AFHTTPBodyPart對象保存要傳輸的內容,並添加到私有屬性bodyStream中
AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
bodyPart.stringEncoding = self.stringEncoding;
bodyPart.headers = mutableHeaders;
bodyPart.boundary = self.boundary;
bodyPart.body = inputStream;
bodyPart.bodyContentLength = (unsigned long long)length;
[self.bodyStream appendHTTPBodyPart:bodyPart];
}
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
{
// 在debug模式下缺乏對應參數會crash
NSParameterAssert(name);
NSParameterAssert(fileName);
NSParameterAssert(mimeType);
// 生成一個可變字典保存請求頭的相關信息,併爲Content-Disposition和Content-Type字段賦值
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
[mutableHeaders setValue:mimeType forKey:@"Content-Type"];
// 調用下下面的那個方法
[self appendPartWithHeaders:mutableHeaders body:data];
}
- (void)appendPartWithFormData:(NSData *)data
name:(NSString *)name
{
// 在debug模式下缺乏對應參數會crash
NSParameterAssert(name);
// 生成一個可變字典保存請求頭的相關信息,併爲Content-Disposition和Content-Type字段賦值
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"];
// 調用下面的那個方法
[self appendPartWithHeaders:mutableHeaders body:data];
}
- (void)appendPartWithHeaders:(NSDictionary *)headers
body:(NSData *)body
{
// 在debug模式下缺乏對應參數會crash
NSParameterAssert(body);
// 生成一個AFHTTPBodyPart對象保存要傳輸的內容,並添加到私有屬性bodyStream中
AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
bodyPart.stringEncoding = self.stringEncoding;
bodyPart.headers = headers;
bodyPart.boundary = self.boundary;
bodyPart.bodyContentLength = [body length];
bodyPart.body = body;
[self.bodyStream appendHTTPBodyPart:bodyPart];
}
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
delay:(NSTimeInterval)delay
{
// 設置發送單個包的大小和請求延遲
self.bodyStream.numberOfBytesInPacket = numberOfBytes;
self.bodyStream.delay = delay;
}
複製代碼
經過對AFURLRequestSerialization
源碼的閱讀,能夠看出AFURLRequestSerialization
這個類是利用用戶傳入的各類參數來實例化NSMutableURLRequest
對象。但這還分爲兩個部分,一個部分是構建普通的請求:如GET
或POST
;另外一部分是構建multipart/form-data
請求。
普通請求的過程是:設置HTTP請求頭、設置mutableRequest
的一些屬性、參數編碼、查詢參數拼接
在- (instancetype)init
方法中分別設置HTTP請求頭Accept-Language
字段和User-Agent
字段。
在- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(nullable id)parameters error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
方法中將用戶自定義的屬性賦值給mutableRequest
對應的字段。
若是請求方式是POST
或者PUT
,還會在- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error
方法中設置Content-Type
字段。
除此以外,用戶還能夠經過暴露的接口爲Authorization
字段賦值
首先,一樣在- (instancetype)init
方法中,對自身的屬性allowsCellularAccess
、cachePolicy
、HTTPShouldHandleCookies
、HTTPShouldUsePipelining
、networkServiceType
、timeoutInterval
設置了KVO。
而後,在KVO方法回調中監聽用戶自定義了哪一個屬性,保存對應的key
和value
。
最後在- (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(nullable id)parameters error:(NSError * _Nullable __autoreleasing *)error;
方法中將value
賦值給mutableRequest
對應的key
,也就是爲mutableRequest
的屬性賦值。
參數編碼提供了三種方式,分別是key0=value0&key1=value1
百分號編碼方式、json
編碼方法和plist
編碼方式,這三種方式能夠經過分別實例化AFHTTPRequestSerializer
對象、AFJSONRequestSerializer
對象和AFPropertyListRequestSerializer
對象來實現。
當時也能夠經過調用- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;
方法,實現block
,來自定義編碼方式。
不過若是請求方式是GET
、HEAD
或者DELETE
,只能經過百分號編碼方式和自定義編碼方式來進行編碼
參數拼接分爲兩種狀況:
第一種,若是請求方式是GET
、HEAD
和DELETE
,就把處理好的查詢參數直接拼接到url
後面;
第二種,請求方式是POST
和PUT
,就把處理好的參數轉成NSData
對象後拼接到請求體中。
multipart/form-data
請求的過程是:設置HTTP請求頭、設置mutableRequest
的一些屬性、實例化AFStreamingMultipartFormData
對象處理數據、上傳時讀取數據
同7.1.1
同7.1.2
首先,在- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(nullable NSDictionary <NSString *, id> *)parameters constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block error:(NSError * _Nullable __autoreleasing *)error;
這個方法中,實例化了AFStreamingMultipartFormData
對象formData
;
而後,將用戶傳入的參數通過解析後轉爲NSData
類型的數據,添加到formData
中;
接着,經過block
回調,將formData
對象暴露給用戶,用戶經過AFStreamingMultipartFormData
類提供的接口,將想要上傳的數據添加到formData
中;
其中,formData
會將傳入的數據,分別轉成AFHTTPBodyPart
對象,而後依次添加到自身屬性bodyStream
中,bodyStream
會將添加進來的AFHTTPBodyPart
對象用數組保存起來;
最後,當數據添加處理完成,formData
就會將bodyStream
賦值給mutableRequest``HTTPBodyStream
屬性,等待數據的讀取。
當發送請求上傳時,NSURLSession
對象會不斷讀取NSURLRequest
對象的屬性HTTPBodyStream
中的數據,在讀取數據時會調用NSInputStream
對象的- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len;
方法;
在AFMultipartBodyStream
類中,重寫了其父類的這個方法,在這其中,經過遍歷屬性HTTPBodyParts
中的數據,將AFHTTPBodyPart
對象中保存的數據讀取出來;
AFHTTPBodyPart
對象經過調用- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length;
方法,並經過狀態機控制對不一樣部分的數據的處理,數據邊讀取邊拼接成帶有格式的字符串,而後再轉換成NSData
類型的數據寫入到buffer中;
除此以外,還能夠經過調用AFStreamingMultipartFormData
類遵照的代理AFMultipartFormData
中的方法- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes delay:(NSTimeInterval)delay;
來設置在上傳數據時,每次發送數據包的最大大小和每次讀取數據包的延遲時間。
源碼閱讀系列:AFNetworking
源碼閱讀:AFNetworking(二)——AFURLRequestSerialization
源碼閱讀:AFNetworking(三)——AFURLResponseSerialization
源碼閱讀:AFNetworking(四)——AFSecurityPolicy
源碼閱讀:AFNetworking(五)——AFNetworkReachabilityManager
源碼閱讀:AFNetworking(六)——AFURLSessionManager
源碼閱讀:AFNetworking(七)——AFHTTPSessionManager
源碼閱讀:AFNetworking(八)——AFAutoPurgingImageCache
源碼閱讀:AFNetworking(九)——AFImageDownloader
源碼閱讀:AFNetworking(十)——AFNetworkActivityIndicatorManager
源碼閱讀:AFNetworking(十一)——UIActivityIndicatorView+AFNetworking
源碼閱讀:AFNetworking(十二)——UIButton+AFNetworking
源碼閱讀:AFNetworking(十三)——UIImageView+AFNetworking
源碼閱讀:AFNetworking(十四)——UIProgressView+AFNetworking