在AFNetworking到底長啥樣(上)中簡單介紹了AFN涉及的主要類及其結構,接下來以一個簡單的POST請求探尋其内部是如何實作的。
伺服器配置
本例中直接使用iMac自帶的Apache,并為其開啟PHP支援。在伺服器目錄下編寫index.php檔案如下:
編寫測試App
建立一個測試App,在主界面上增加一個按鈕,在按鈕的點選函數中發起網絡請求,如下:
啟動測試App,點選按鈕。接下裡讓我們看看AFN是如何優雅的管理網絡請求的。
AFHTTPSessionManager的初始化
<code>self.manager</code>是<code>AFHTTPSessionManager</code>的執行個體,它使用類方法<code>+ manager</code>初始化。這個類方法最終調用如下方法:
①中調用父類的方法初始化父類相關屬性,這包括:
②中設定其requestSerializer為AFHTTPRequestSerializer執行個體,③重設其responseSerializer為AFJSONResponseSerializer執行個體。
AFHTTPRequestSerializer的初始化
requestSerializer被設定成了AFHTTPRequestSerializer的執行個體,初始化是通過類方法<code>+serializer</code>實作的。内容包括:
AFJSONResponseSerializer的初始化
responseSerializer被設定成為了AFJSONResponseSerializer的執行個體,初始化是通過類方法<code>+serializer</code>實作的。内容包括:
至此必要的初始化已完成。
進入到POST函數中:
即最終生成一個dataTask,然後resume啟動,并将dataTask傳回。
生成的dataTask是通過如下函數實作的,本例中傳入的參數值也已标明:
其内部執行的步驟如下:
Step1:調用requestSerializer的如下方法建立mutableRequest:
①是調用AFURLRequestSerialization協定方法對mutableRequest進行進一步處理,包括:
設定預設header,如在requestSerializer初始化時設定預設header:User-Agent和Accept-Language
處理參數
若使用者自定義了處理參數的block(self.queryStringSerialization),則使用該block;否則使用預設的處理方式,會被最終處理成如下格式:name=layne&age=30。
根據HTTPMethod決定參數串放到哪裡:GET/HEAD/DELETE直接拼接到url;其他(如POST)方法放到HTTPBody中(使用stringEncoding(NSUTF8StringEncoding)編碼),并設定Content-Type為application/x-www-form-urlencoded。
至此,生成的mutableRequest結構如下:
Step2:合并傳入的header
執行之後mutableRequest結構如下:
Step3:判斷Step1中生成mutableRequest的過程是否出錯,若出錯,則調用failureBlock并傳回。
Step4:根據mutableRequest生成dataTask。
說明:
①中不直接生成dataTask,是因為在iOS8以下的系統上,若是在并行隊列上建立dataTask會導緻completionHandler調用出錯。是以為了解決這個問題,針對iOS8以下系統,AFN使用自己維護的一個串行隊列來建立dataTask。具體問題描述如下:
Due to this bug: http://openradar.appspot.com/radar?id=5871104061079552 in NSURLSessionTask, creating tasks on a concurrent queue can cause incorrect completionHandlers to get called. When a duplicate taskIdentifier is returned by the task, the previous completionHandler gets cleared out and replaced with the new one. If the data for the first request comes back before the second request's data, the first response is then called against the second completionHandler. I'm not sure what AFNetworking should do here — it could enforce creating tasks on a serial queue or it could just advise people to do so. We could also add a test to assert that the taskIdentifier is not a duplicate?
②的作用是為dataTask生成對應的delegate,以調用回調方法。
a. 為dataTask生成AFURLSessionManagerTaskDelegate執行個體。delegate内部維護着:
一個mutableData用來儲存收到的response data。
一個uploadProgress和downloadProgress用來标明上傳/下載下傳進度。它們的cancel、suspend和resume操作與dataTask進行了綁定,即通過對它們進行cancel、suspend和resume操作就可以操作dataTask的cancel、suspend和resume。此外,還采用KVO監聽uploadProgress和downloadProgress的進度變化,進而調用使用者自定義的block:uploadProgressBlock和downloadProgressBlock。
b. delegate弱引用目前的manager。
c.delegate儲存完成回調。
③傳回最終的dataTask,并啟動。
AFN是基于NSURLSession的,涉及以下幾個協定:
NSURLSessionDelegate
NSURLSessionTaskDelegate
NSURLSessionDataDelegate
NSURLSessionDownloadDelegate
回調調用順序:
① URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
由于使用的是POST方式,是以該回調是首先調用的。邏輯:找到task對應的delegate=>調用delegate的URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:來更新delegate維護的uploadProgress。若使用者自定義了taskDidSendBodyData block,則調用。
② URLSession:task:didFinishCollectingMetrics:
該方法調用時機不定,用來收集整個請求的資訊。邏輯:找到task對應的delegate=>調用delegate的URLSession:task:didFinishCollectingMetrics:來為delegate.sessionTaskMetrics指派。若使用者自定義了taskDidFinishCollectingMetrics block,則調用。
③ URLSession:dataTask:didReceiveData:
收到response data時執行。邏輯:找到task對應的delegate=>調用delegate的URLSession:task:didCompleteWithError:來更新delegate内部維護的downloadProgress并使用mutableData儲存response data。若使用者自定義了dataTaskDidReceiveData block,則調用。
④ URLSession:task:didCompleteWithError:
這是最後執行的回調。邏輯:找到task對應的delegate=>調用delegate的URLSession:task:didCompleteWithError:,并删除delegate和task的對應關系。若使用者自定義了taskDidComplete block,則調用。
資料的處理主要在delegate的URLSession:task:didCompleteWithError:中,主要做了以下幾件工作:
構造userInfo字典:
若出錯,則直接調用completeHandler回調,之後将上面的userInfo以通知AFNetworkingTaskDidCompleteNotification的形式廣播出去。
若未出錯,則使用responseSerializer(AFJSONResponseSerializer執行個體)将data轉換為NSDictionary。若轉換過程未出錯,則在userInfo中增加{AFNetworkingTaskDidCompleteSerializedResponseKey:responseObject};若出錯,則增加{AFNetworkingTaskDidCompleteErrorKey:error}。之後調用completeHandler回調,并将userInfo以通知AFNetworkingTaskDidCompleteNotification的形式廣播出去。
經過上面的處理,最終要麼error要麼responseObject會傳回到POST的回調中去。整個網絡請求就完成了。
上面我們跟着一個簡單的POST請求了解了AFN的整個工作流程,但還有一些細節我覺得還是值得我們學習的。
如果response不是标準格式的JSON資料或者我們需要對原始data進行加密解密操作該如何做?
答:将responseSerializer設定為AFHTTPResponseSerializer的執行個體,這樣response data 會以原始data的形式傳回給上層。AFHTTPResponseSerializer僅針對code和contenttype進行校驗,而預設的AFJSONResponseSerializer除了校驗code和contenttype之外還對JSON格式進行校驗,并直接解析成NSDictionary。當然,如果想對response data想要做最全面的自定義處理,最直接的方式當然是自定義AFHTTPResponseSerializer的子類,并重寫協定方法
如果傳回的JSON資料中包含NSNull資料該如何處理?
答:将responseSerializer(AFJSONResponseSerializer執行個體)的removesKeysWithNullValues屬性設定為YES。這樣一來在JSON轉換為NSDictionary之後會将NSDictionary中的NSNull值去除。
如果我想自己更改請求參數的格式(即不用預設的name=layne&age=30這種)該如何設定?
答:調用requestSerializer的-setQueryStringSerializationWithBlock:設定自定義的格式。在requestSerializer處理參數的時候會先去判斷queryStringSerialization block是否為空,若不為空,則使用該block處理參數。若為空,則使用預設的格式(如name=layne&age=30)生成參數串。
除了使用GET/POST方法的回調,還可以通過什麼方式獲得網絡請求結果?
答:還可以使用notification。AFN中有一個名為AFNetworkingTaskDidCompleteNotification的通知,可以通過監聽該通知擷取網絡請求結果。AFURLSessionManagerTaskDelegate的URLSession:task:didCompleteWithError:中在調用completeHandler之後會發送通知:
如何更改回調所在的線程?
答:指定manager的completeQueue。這樣回調就會在其他線程中執行。
AFURLSessionManager如何擷取NSURLSession維護的task?
答:使用getTasksWithCompletionHandler:API并使用信号量保證線程安全。
若我想取消一個剛發起的網絡請求該如何做?
答:AFHTTPSessionManager的POST/GET方法傳回的是task對象,直接[task cancel]即可。如:
若我想取消所有task該如何做?
答:使用如下方法。建議resetSession傳入YES将session重置,否則下次網絡請求會crash,并提示:“Attempted to create a task in a session that has been invalidated”
至此AFNetworking是如何工作的我們就知道了。看了源碼之後不得不感歎作者真是神人,不僅優雅的給出了NSURLSession的使用範例,而且還包含了業務層面的巧妙設計。嗯,閱讀源碼使我快樂^_^。