原文:http://itangqi.me/2016/05/15/the-notes-of-learning-afnetworking-five/
AFURLRequestSerialization
AFURLRequestSerialization
定義爲協議,其主要工作是對發出的 HTTP 請求進行處理:
1 2 3 4 5 6 7 |
@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying> - (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(nullable id)parameters error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW; @end |
遵循該協議的類同時也要遵循 NSObject、NSSecureCoding 和 NSCopying 這三個協議,以實現 Objective-C 對象的基本行爲、安全編碼以及拷貝。
AFHTTPRequestSerializer
而在 AFURLRequestSerialization
模塊中,最爲重要的類便是 AFHTTPRequestSerializer
,其主要作用爲:
- 處理查詢的 URL 參數
- 設置 HTTP 頭部字段
- 設置請求的屬性
- 分塊上傳
這篇文章不會對其中涉及分塊上傳的部分進行分析,因爲其中涉及到了多個類的功能,比較複雜,如果有興趣可以研究一下。
處理查詢參數
處理查詢參數這部分主要是通過 AFQueryStringPair
還有一些 C 函數來完成的,這個類有兩個屬性
field
和 value
對應 HTTP 請求的查詢 URL 中的參數。
我們來看初始化方法,其中的 - (NSString *)URLEncodedStringValue
方法會返回 key=value
這種格式,同時使用
AFPercentEscapedStringFromString
函數來對 field
和 value
進行處理,將其中的
:#[]@!$&'()*+,;=
等字符轉換爲百分號表示的形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
- (instancetype)initWithField:(id)field value:(id)value { self = [super init]; if (!self) { return nil; } self.field = field; self.value = value; return self; } - (NSString *)URLEncodedStringValue { if (!self.value || [self.value isEqual:[NSNull null]]) { return AFPercentEscapedStringFromString([self.field description]); } else { return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])]; } } |
這一部分代碼還負責返回查詢參數,將 AFQueryStringPair
或者 key
value
轉換爲以下這種形式:
1
|
username = dravenss&password=123456&hello[world]=helloworld
|
它的實現主要依賴於一個遞歸函數 AFQueryStringPairsFromKeyAndValue
,如果當前的 value
是一個集合類型的話,那麼它就會不斷地遞歸調用自己:
1 2 3 4 5 6 7 8 |
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) { NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)]; ... return mutableQueryStringComponents; } |
最後返回一個數組
1 2 3 4 5 |
[ username = draveness, password = 123456, hello[world] = helloworld ] |
得到這個數組之後就會調用 AFQueryStringFromParameters
使用 &
來拼接它們:
1 2 3 4 5 6 7 8 |
static NSString * AFQueryStringFromParameters(NSDictionary *parameters) { NSMutableArray *mutablePairs = [NSMutableArray array]; for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { [mutablePairs addObject:[pair URLEncodedStringValue]]; } return [mutablePairs componentsJoinedByString:@"&"]; } |
設置 HTTP 頭部字段
AFHTTPRequestSerializer
在頭文件中提供了一些屬性方便我們設置 HTTP 頭部字段。同時,在類的內部,它提供了
- [AFHTTPRequestSerializer setValue:forHTTPHeaderField:]
方法來設置 HTTP 頭部,其實它的實現都是基於一個名爲
mutableHTTPRequestHeaders
的屬性的:
1 2 3 4 5 6 7 8 9 |
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field { [self.mutableHTTPRequestHeaders setValue:value forKey:field]; } - (NSString *)valueForHTTPHeaderField:(NSString *)field { return [self.mutableHTTPRequestHeaders valueForKey:field]; } |
在設置 HTTP 頭部字段時,都會存儲到這個可變字典中。而當真正使用時,會用 HTTPRequestHeaders
這個方法,來獲取對應版本的不可變字典:
1 2 3 |
- (NSDictionary *)HTTPRequestHeaders { return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders]; } |
到了這裏,可以來分析一下,這個類是如何設置一些我們平時常用的頭部字段的。首先是 User-Agent
,在 AFHTTPRequestSerializer
剛剛初始化時,就會根據當前編譯的平臺生成一個
userAgent
字符串:
1 2 3 |
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]]; [self setValue:userAgent forHTTPHeaderField:@"User-Agent"]; |
設置驗證字段時,可以使用 - [AFHTTPRequestSerializer setAuthorizationHeaderFieldWithUsername:password:]
方法:
1 2 3 4 5 6 7 |
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username password:(NSString *)password { NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding]; NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]; [self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"]; } |
設置請求的屬性
還有一些 NSURLRequest
的屬性是通過另一種方式來設置的,AFNetworking 爲這些功能提供了接口:
1 2 3 4 5 6 7 8 9 10 11 |
@property (nonatomic, assign) BOOL allowsCellularAccess; @property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy; @property (nonatomic, assign) BOOL HTTPShouldHandleCookies; @property (nonatomic, assign) BOOL HTTPShouldUsePipelining; @property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType; @property (nonatomic, assign) NSTimeInterval timeoutInterval; |
它們都會通過 AFHTTPRequestSerializerObservedKeyPaths
的調用而返回:
1 2 3 4 5 6 7 8 9 |
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; } |
在這些屬性被設置時,會觸發 KVO,然後將新的屬性存儲在一個名爲 mutableObservedChangedKeyPaths
的字典中:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(__unused id)object change:(NSDictionary *)change context:(void *)context { if (context == AFHTTPRequestSerializerObserverContext) { if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) { [self.mutableObservedChangedKeyPaths removeObject:keyPath]; } else { [self.mutableObservedChangedKeyPaths addObject:keyPath]; } } } |
然後會在生成 NSURLRequest
的時候設置這些屬性:
1 2 3 4 5 6 7 8 |
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url]; mutableRequest.HTTPMethod = method; for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) { [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath]; } } |
關於這個方法的的具體實現會在下一節中介紹。
工作流程
AFHTTPRequestSerializer
會在 AHHTTPSessionManager
初始化時一併初始化,這時它會根據當前系統環境預設置一些 HTTP 頭部字段
Accept-Language
User-Agent
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
- (instancetype)init { self = [super init]; if (!self) { return nil; } self.stringEncoding = NSUTF8StringEncoding; self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary]; #1: 設置接收語言,用戶代理,略 // HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil]; self.mutableObservedChangedKeyPaths = [NSMutableSet set]; for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext]; } } return self; } |
同時它還對一些屬性進行 KVO,確保它們在改變後更新 NSMutableURLRequest
中對應的屬性。
在初始化之後,如果調用了 - [AFHTTPSessionManager dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:]
,就會進入
AFHTTPRequestSerializer
的這一方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(method); NSParameterAssert(URLString); NSURL *url = [NSURL URLWithString:URLString]; // 1. 對參數進行檢查 NSParameterAssert(url); NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url]; // 2. 設置 HTTP 方法 mutableRequest.HTTPMethod = method; // 3. 通過 `mutableObservedChangedKeyPaths` 字典設置 `NSMutableURLRequest` 的屬性 for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) { [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath]; } } // 設置 HTTP 頭部字段和查詢參數 mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy]; return mutableRequest; } |
- [AFHTTPRequestSerializer requestBySerializingRequest:withParameters:error:]
方法主要做了以下幾件事情:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(request); NSMutableURLRequest *mutableRequest = [request mutableCopy]; // 1. 通過 `HTTPRequestHeaders` 字典設置頭部字段 [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (![request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }]; NSString *query = nil; if (parameters) { if (self.queryStringSerialization) { NSError *serializationError; query = self.queryStringSerialization(request, parameters, &serializationError); if (serializationError) { if (error) { *error = serializationError; } return nil; } } else { switch (self.queryStringSerializationStyle) { case AFHTTPRequestQueryStringDefaultStyle: // 2. 調用 `AFQueryStringFromParameters` 將參數轉換爲查詢參數 query = AFQueryStringFromParameters(parameters); break; } } } // 3. 將 parameters 添加到 URL 或者 HTTP body 中 if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { if (query) { mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; } } 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]]; } // 4. 將 parameters 添加到 URL 或者 HTTP body 中 return mutableRequest; } |
小結
AFURLResponseSerialization
負責對返回的數據進行序列化AFURLRequestSerialization
負責生成NSMutableURLRequest
,爲請求設置 HTTP 頭部,管理髮出的請求