本文轉載請註明出處 —— polobymulberry-博客園html
上一篇中咱們在iOS Example代碼中提到了AFHTTPSessionManager中的一個函數:ios
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id)parameters progress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
這個函數做用其實看函數名就明白了- 使用GET類型的Request來建立並運行一個NSURLSessionDataTask。git
具體咱們看函數實現:github
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:downloadProgress success:success failure:failure]; [dataTask resume]; return dataTask;
咱們很天然想到了,全部的關鍵都在dataTaskWithHTTPMethod這個函數。咱們先不慌看這個函數的具體實現,先窮盡到這個函數的全部調用。咱們已經知道這個函數是建立一個NSURLSessionDataTask,而系統提供給咱們建立NSURLSessionDataTask的方法,有兩個:編程
好,那咱們就沿着這個線索一直找下去,一直找到有這兩個函數使用的地方。追蹤溯源,還真找到了這樣一條函數調用棧。api
上圖能夠看出GET、HEAD、POST、PUT、PATCH、DELETE這些方法實現的不一樣之處只在於調用dataTaskWithHTTPMethod:傳遞的method名稱不一樣。另外在調用dataTaskWithRequest:時候,其實已經在上一級函數dataTaskWithHTTPMethod:中構建好了一個NSMutableURLRequest類型的request。因此咱們主要研究dataTaskWithHTTPMethod:函數實現。緩存
dataTaskWithHTTPMethod函數的實現主要分兩部分,一部分是構建NSMutableURLRequest,另外一部分是根據已構建好的Request來構建NSURLSessionDataTask。sass
此處構建request分爲兩個部分:cookie
先直接暴力列出requestWithMethod的函數聲明(注:requestWithMethod是AFHTTPRequestSerializer的一個成員函數,而且AFHTTPRequestSerializer遵循AFURLRequestSerialization協議)網絡
/** 使用指定的HTTP method和URLString來構建一個NSMutableURLRequest對象實例 若是method是GET、HEAD、DELETE,那parameter將會被用來構建一個基於url編碼的查詢字符串(query url) ,而且這個字符串會直接加到request的url後面。對於其餘的Method,好比POST/PUT,它們會根 據parameterEncoding屬性進行編碼,然後加到request的http body上。 @param method request的HTTP methodt,好比 `GET`, `POST`, `PUT`, or `DELETE`. 該參數不能爲空 @param URLString 用來建立request的URL @param parameters 既能夠對method爲GET的request設置一個查詢字符串(query string),也能夠設置到request的HTTP body上 @param error 構建request時發生的錯誤 @return 一個NSMutableURLRequest的對象 */ - (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(nullable id)parameters error:(NSError * _Nullable __autoreleasing *)error;
接着咱們來看requestWithMethod的具體實現:
NSParameterAssert(method); NSParameterAssert(URLString); NSURL *url = [NSURL URLWithString:URLString]; NSParameterAssert(url);
其中NSParameterAssert(method) <=> NSParameterAssert(method != nil),同理NSParameterAssert(URLString)和NSParameterAssert(url)也同樣。這裏NShipster給出了一個金科玉律:
方法或函數應當在代碼最開始處使用 NSParameterAssert
/ NSCParameterAssert
來強制輸入的值知足先驗條件,這是一條金科玉律;其餘狀況下使用 NSAssert
/ NSCAssert
。
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
NSURLRequest/NSMutableURLRequest須要賦值的屬性能夠在AFHTTPRequestSerializerObservedKeyPaths()中找到,咱們能夠進去看一下:
// 定義了一個static的方法,表示該方法只能在本文件中使用 // 函數總體上使用了單例模式 static NSArray * AFHTTPRequestSerializerObservedKeyPaths() { static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil; static dispatch_once_t onceToken; // 此處須要observer的keypath爲allowsCellularAccess、cachePolicy、HTTPShouldHandleCookies // HTTPShouldUsePipelining、networkServiceType、timeoutInterval dispatch_once(&onceToken, ^{ _AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))]; }); return _AFHTTPRequestSerializerObservedKeyPaths; }
簡單介紹下上面添加的keypath:
/** 是否容許使用設備的蜂窩移動網絡來建立request,默認爲容許: */ @property (nonatomic, assign) BOOL allowsCellularAccess; /** 建立的request所使用的緩存策略,默認使用`NSURLRequestUseProtocolCachePolicy`,該策略表示 若是緩存不存在,直接從服務端獲取。若是緩存存在,會根據response中的Cache-Control字段判斷 下一步操做,如: Cache-Control字段爲must-revalidata, 則 詢問服務端該數據是否有更新,無更新話 直接返回給用戶緩存數據,若已更新,則請求服務端. */ @property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy; /** 若是設置HTTPShouldHandleCookies爲YES,就處理存儲在NSHTTPCookieStore中的cookies HTTPShouldHandleCookies表示是否應該給request設置cookie並隨request一塊兒發送出去 */ @property (nonatomic, assign) BOOL HTTPShouldHandleCookies; /** HTTPShouldUsePipelining表示receiver(理解爲iOS客戶端)的下一個信息是否必須等到上一個請求回覆才能發送。 若是爲YES表示能夠,NO表示必須等receiver收到先前的回覆才能發送下個信息。 */ @property (nonatomic, assign) BOOL HTTPShouldUsePipelining; /** 設定request的network service類型. 默認是`NSURLNetworkServiceTypeDefault`. 這個network service是爲了告訴系統網絡層這個request使用的目的 好比NSURLNetworkServiceTypeVoIP表示的就這個request是用來請求網際協議通話技術(Voice over IP)。
系統能根據提供的信息來優化網絡處理,從而優化電池壽命,網絡性能等等 */ @property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType; /** 超時機制,默認60秒 */ @property (nonatomic, assign) NSTimeInterval timeoutInterval;
而後經過判斷mutableObservedChangedKeyPaths(NSMutableSet)中是否有這個keyPath,來設定mutableRequest對應的keyPath值。
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) { [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath]; } }
至於mutableObservedChangedKeyPaths是什麼,咱們能夠在AFURLRequestSerialization文件中的observeValueForKeyPath函數中獲得答案。整個過程是這樣的:
關鍵就是在哪裏會產生keypath的值變化了的消息?
也就是說你只要使用了keyPath對應的的setter方法,就會響應observerValueForKeyPath這個方法,從而將對應的keyPath添加到了mutableObservedChangedKeyPaths。至於添加keyPath到observer中,那是在AFHTTPRequestSerializer的init中乾的活:
此過程主要集中在requestBySerializingRequest這個函數中。在介紹requestBySerializingRequest以前,先簡單介紹下,爲何會有這個函數的存在?
通常咱們請求都會按key=value的方式帶上各類參數,GET方法參數直接加在URL上,POST方法放在body上,NSURLRequest沒有封裝好這個參數的解析,只能咱們本身拼好字符串。AFNetworking提供了接口,讓參數能夠是NSDictionary, NSArray, NSSet這些類型,再由內部解析成字符串後賦給NSURLRequest。
轉化過程大體是這樣的:
@{ @"name" : @"bang", @"phone": @{@"mobile": @"xx", @"home": @"xx"}, @"families": @[@"father", @"mother"], @"nums": [NSSet setWithObjects:@"1", @"2", nil] } -> @[ field: @"name", value: @"bang", field: @"phone[mobile]", value: @"xx", field: @"phone[home]", value: @"xx", field: @"families[]", value: @"father", field: @"families[]", value: @"mother", field: @"nums", value: @"1", field: @"nums", value: @"2", ] -> name=bang&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&num=2
或者看下面這段解釋:
好比說我定義了下面這個parameter:
NSString *URLString = @"http://example.com"; NSDictionary *parameters = @{@"foo": @"bar", @"baz": @[@1, @2, @3]};
使用GET方式,最後獲得的request是這樣的:
[[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:URLString parameters:parameters error:nil]; GET http://example.com?foo=bar&baz[]=1&baz[]=2&baz[]=3
或者使用POST方式,最後獲得的request是這樣的:
[[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters]; POST http://example.com/ Content-Type: application/x-www-form-urlencoded foo=bar&baz[]=1&baz[]=2&baz[]=3
requestBySerializingRequest也分爲三個部分:
設置request的http header field:
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (![request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }];
這裏關於http header field的值都存放在了HTTPRequestHeaders中了。至於HTTPRequestHeaders的設置,是在多個函數中都有設置的。此處就不一一贅述,後面遇到會詳解。
根據parameter來構建查詢字符串,這裏一開始parameter以下:
Printing description of parameters: { baz = ( 1, 2, 3 ); foo = bar; }
通過構建後,獲得query爲(這個例子中的構建方式使用的是AFQueryStringFromParameters()函數):
Printing description of query: baz[]=1&baz[]=2&baz[]=3&foo=bar
事實上代碼中有兩種構建query的方式:
其中一種就是,若是自定義了queryStringSerialization(AFQueryStringSerializationBlock的block變量)。那麼就使用自定義的queryStringSerialization構建方式(此方法在AFNetworking的test中用的比較多)
還有一種就是上面的那個AFQueryStringFromParameters()函數,咱們能夠看到AFQueryStringFromParameters的調用結構是下圖這樣的:
講解的話,我以爲根據上圖從後往前講比較好:
首先看NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value)這個函數
該函數首先定義了一個NSSortDescriptor *sortDescriptor:
// 根據須要排列的對象的description來進行升序排列,而且selector使用的是compare: // 由於對象的description返回的是NSString,因此此處compare:使用的是NSString的compare函數 // 即@[@"foo", @"bar", @"bae"] ----> @[@"bae", @"bar",@"foo"] NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
接着會對value的類型進行判斷,有NSDictionary、NSArray、NSSet類型。不過有人就會問了,在AFQueryStringPairsFromDictionary中給AFQueryStringPairsFromKeyAndValue函數傳入的value不是NSDictionary嘛?還要判斷那麼多類型幹啥?對,問得很好,這就是AFQueryStringPairsFromKeyAndValue的核心----遞歸調用並解析,你不能保證NSDictionary的value中存放的是一個NSArray、NSSet。
既然是遞歸,那麼就要有結束遞歸的狀況,好比解析到最後,對應value是一個NSString,那麼就得調用函數中最後的else語句:
else { [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]]; }
注意此處定義了一個AFQueryStringPair:
@interface AFQueryStringPair : NSObject @property (readwrite, nonatomic, strong) id field; @property (readwrite, nonatomic, strong) id value; // ... @end
而initWithField作的就是將key賦給field,value賦值給value。你們能夠回頭看一下最開始舉的那個例子,就產生了對應的field-value。
接着回到AFQueryStringPairsFromDictionary函數,好像沒啥好說的。再回到AFQueryStringFromParameters函數,這個函數就是把這些構建好的AFQueryStringPair一個個用&鏈接好。這裏注意一點就是,此處會對AFQueryStringPair使用其URLEncodedStringValue函數作必定的處理,其實就是Percent-encoding(百分號編碼)。
知識點:百分號編碼
根據RFC 3986,如下字符爲保留字:
另外,在RFC 3989 – Section 3.4部分,「?」和「/」看成爲URL中的query string的時候,再也不當作保留字。
此處主要是經過stringByAddingPercentEncodingWithAllowedCharacters函數來給咱們的string進行百分號編碼的。其中stringByAddingPercentEncodingWithAllowedCharacters函數須要傳入不須要百分號編碼的字符集(也就是不包括上面說的保留字,即函數中構建的allowedCharacterSet)。另外,爲了防止字符形成的問題,此處還須要使用rangeOfComposedCharacterSequencesForRange函數來處理字符長度。
舉個例子,若是我傳入的字符串爲,那麼最終獲得的百分號編碼的字符串爲
poloby%3A%23mulberry%5B%5D%40%F0%9F%91%B4%F0%9F%8F%BB%F0%9F%91%AE%F0%9F%8F%BD
下圖是保留字的百分號編碼:
因此事實上,上面最終生成的query url中,[]都會被%5B%5D所代替。
最後判斷該request中是否包含了GET、HEAD、DELETE(都包含在HTTPMethodsEncodingParametersInURI)。由於這幾個method的quey是拼接到url後面的。而POST、PUT是把query拼接到http body中的。
若是method是GET、HEAD、DELETE等。最後將query合併到mutbleRequest的query url上。不過這裏仍是要分狀況討論,若是request的query url不爲空,就在生成的query前拼接&字符,再拼接到原先的query url上,若是request的query url爲空,就將生成的的query前拼接?字符,再拼接到request的url上
Printing description of mutableRequest: <NSMutableURLRequest: 0x7f9a63f237c0> { URL: https://api.app.net/stream/0/posts/stream/global?baz%5B%5D=1&baz%5B%5D=2&baz%5B%5D=3&foo=bar }
若是method是POST、PUT等。最後將query設置到http body上。另外,在此以前,函數會判斷request的Content-Type是否設置了,若是沒有,就默認設置爲application/x-www-form-urlencoded。
生成request出錯了怎麼辦?這裏冒出了一個completionQueue,暫時無論它,由於我並不知道這個東西是怎麼用的。通常的話,咱們都是在main queue來執行自定義的failure函數處理error。
有了request後,就能夠調用-[AFURLSessionManager dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:]來構建session data task。
一樣地,dataTaskWithRequest函數也分爲兩個部分。第一部分是建立一個dataTask,第二個部分是調用addDelegateForDataTask這個函數,具體這個函數是作什麼的,目前我也不是很清楚。
使用了url_session_manager_create_task_safely(dispatch_block_t block)這個函數。這個函數主要的目的是爲了解決iOS8以前的一個bug,詳見https://github.com/AFNetworking/AFNetworking/issues/2093。在這個issue中,提問者建議版本小於iOS8的使用QUEUE_SERIAL的dispatch。因此纔有了url_session_manager_create_task_safely這個函數,注意函數名中的create task和safely。因爲在iOS8以後,這個bug被修復了,因此直接調用block()便可。
static void url_session_manager_create_task_safely(dispatch_block_t block) { if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) { // Fix of bug // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8) // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093 dispatch_sync(url_session_manager_creation_queue(), block); } else { block(); } }
字面上理解的話,就是給data task添加了一個delegate,而這個delegate的類型爲AFURLSessionManagerTaskDelegate。爲何要給task加一個delegate?
咱們看看AFURLSessionManagerTaskDelegate的定義:
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
這裏我比較疑惑,NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate這三個delegate應該NSURLSession的delegate,你這邊出現了一個AFURLSessionManagerTaskDelegate也來實現這三個delegate是幾個意思?我猜想這裏是否是一種分離的代碼的方式,就是說把NSURLSession的delegate的實現分離出來給AFURLSessionManagerTaskDelegate實現。可是搜索了一下AFURLSessionManager中的session屬性的構建:
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
這裏的delegate並非使用了AFURLSessionManagerTaskDelegate的那個delegate,因此上述猜想錯誤。不過我仍是找到了點蛛絲馬跡:
AFURLSessionManager中session(NSURLSession)的delegate設置爲了AFURLSessionManager的self,而且AFURLSessionManager確實也遵循了NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate這三個協議,也實現了其中的方法。關鍵是實現這些方法時用到了AFURLSessionManagerTaskDelegate的delegate中實現的方法。至於爲何要這麼作,話說我也是剛看,因此還須要消化一下。
這一篇就到此爲止,下面一篇會詳細介紹實現的NSURLSession的delegate方法了。