標籤:
來源:zongmumask
連結:http://www.jianshu.com/p/8eac5b1975de
簡述
在iOS開發中,與直接使用蘋果架構中提供的NSURLConnection或NSURLSession進行網路請求相比,使用AFNetworking會有哪些好處?當同時發起多個網路請求AFNetworking是如何?並發的,在並發的時候,AFNetworking是如何管理線程的?蘋果重構NSURLConnetion推出新的網路載入系統NSURLSession解決了什麼問題或者是與NSURLConnection相比NSURLSession有好些好處?上面問題的答案會貫穿在這篇文章中(本篇文章只涉及了與操作隊列,多線程相關的分析)。
不用網路架構進行網路請求
NSURLConnection的簡單使用(下面的代碼均只為了示範,更詳細的使用方法請自行Google)
NSURLConnection提供了兩個類方法用於發起同步或非同步請求,對於非同步請求來說必然是在子線程中發起,若在主線程中發起非同步網路請求會造成主線程阻塞,介面無響應,這就涉及到多線程編程。但多線程編程是一門非常細緻的活,要考慮很多的問題,比如線程的生命週期,多線程資源競爭,加鎖,避免死結,稍不留意就會踩到坑裡。好在蘋果的介面使用極其方便,你甚至不需要理解多線程就能便於使用上多線程了,發起非同步請求的介面內部已經實現了多線程相關的操作。越是進階的介面,其隱藏的細節就越多,對於開發人員來說當然是很方便,但若還能對其實現有所理解那就更完美了。當子線程發起了非同步請求後會阻塞以等待網路響應,那應該由誰來處理網路響應呢?蘋果提供了兩種方式,一種是block,提供一個處理響應的block回調。一種是代理,使用代理的話就必須實現NSURLConnectionDelegate這個協議。你只需要在有網路請求的UIViewController中調用NSURLConnection提供的類方法就可以了。但如果你的項目中有不止一個UIViewController或者有的UIViewController中都不止一個請求的話,你就需要在每一個有網路請求的UIViewController中這樣寫。這樣寫會有什麼問題呢?首先會造成軟體結構不清晰,沒有剝離出網路層,其次沒有實現網路請求統一管理,無法實現取消所有網路請求等功能,再有會出現很多重複的代碼,沒有讓公用功能形成模組,進行複用。當然為瞭解決這些問題你也可以自己實現自己的網路架構。
使用NSURLConnection版本的AFNetworking
使用網路架構的好處在於可以將分散在各個視圖控制器中的網路請求統一起來模組化形成網路層,降低與資料層和表現層的耦合。AFNetworking就做了這樣的工作。網路上已經有很多分析基於NSURLConnection實現的AFNetworking 2.x的原始碼,這裡只簡單說一下其實現整個流程,想要深入瞭解的請Google或查看其原始碼。首先AFNetworking要解決實現介面統一和所有網路請求統一管理的問題。NSURLConnection發起有兩種方式發起請求,分別是設定響應block和代理,那採用哪種方式能夠實現網路請求統一管理呢?block肯定不行,因為傳入響應回調block的[NSURLConnection sendAsynchronousRequest:queue:completionHandler:]方法會立即執行。那麼只能使用設定響應代理這種方式了。具體實現是將網路請求繼承於NSOperation,生產網路請求,然後再將這個網路請求加入操作隊列裡,剩下的所有與線程相關的操作都由這個操作隊列去實現。越進階易容的介面就隱藏越多的實現細節,NSOperation隱藏了線程相關的所有細節,使得開發人員只需關心構建什麼樣的操作。AFNetworking甚至隱藏了操作,操作隊列的概念,使得開發人員只需關心如何設定網路相關的請求參數,響應回調等,而不用再關心當多個網路請求如何進行統一管理,這些都已經由AFNetworking內部的操作隊列屬性完成了。那需要為每個請求建立一個線程來發起請求嗎?基於NSURLConnection的AFNetworking是只建立了一條線程來發起所有請求並阻塞以等待響應。
重構推出的NSURLSession解決了NSURLConnection哪些問題
從2013年蘋果發布重構後的載入系統NSURLSession,AFNetworking這個最受歡迎的網路架構也隨之發布了基於NSURLSession的實現版本。這篇文章從 NSURLConnection 到 NSURLSession對NSURLSession的使用做了介紹。既然是對NSURLConnection進行的重構,那一定是解決了NSURLConnection存在的一些問題。蘋果的介面以及其簡單的形式呈現給開發人員,盡量把複雜,容易出錯的地方以簡單的介面暴露出來,這使得使用者能夠參考文檔很快上手,但這也有一些弊端,就是開發人員對其中的原理不是很瞭解。越進階的介面越容易使用,但其隱形實現細節就越多。相信在進行多線程編程的時候,很多開發人員遇到過各種各樣的坑,但在iOS平台上,蘋果推出了GCD,NSperation等一系列介面可以讓開發人員完全可以只關注業務的實現,這些介面內部已經替開發人員管理好了線程的建立,銷毀,多線程資源競爭等需要開發人員費很多精力的事情,甚至開發人員不用對多線程理解透徹都能把多線程用得得心應手。NSURLConnection已經隱藏了線程相關的操作,已經給開發人員減輕了很多負擔。但NSURLConnectoin只隱藏了單個網路請求的線程的相關操作,並沒有提供介面來解決多個網路請求時多個線程的管理問題,譬如當有多個網路請求時是否應該使用線程池來避免不停建立與銷毀線程(這個可以有NSOperationQueue很好的解決)。並且NSURLConnection不是基於HTTP/2協議的,若使用NSURLConnection發起請求則每次請求都需要經過三向交握過程,可見NSURLConnection確實有很多可以最佳化的地方(我只發現這些)。NSURLConnection存在的無法將多個請求關聯起來的問題已經很好的由AFNetworking解決了,所以推出的NSURLSession可以說是借鑒了AFNetworking的思想,並且可以從AFNetworking的原始碼中很容易的看出來。基於NSURLConnection的AFNetworking需要讓網路請求繼承與NSOperation,然後再將該產生的網路請求加入操作隊列中,但基於NSURLSessioin的AFNetworking只需要建立一個網路請求任務就可以了,原因在於,NSURLSession內部已經維護了兩個操作隊列,一個是處理session的相關回調,一個是處理響應相關的回調,所以說NSURLSession是借鑒了AFNetworking的繼承於NSOperation和用單線程發起並等待響應的思想(這個會在後面給出證明)。
基於NSURLSession的AFNetworking的源碼分析
是基於NSURLSession的AFNetworking的UML圖(只為展示類之間的關聯關係,並沒有給出每個類的所有屬性和方法):
從該類圖已經能夠明白AFNetworking整個的工作流程。
下面進行程式碼分析,使用AFHTTPSessionManager進行網路請求的範例程式碼(具體的使用請參考AFNetworking的README文檔):
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:URL parameters:nil progress:nil success:^(NSURLSessionDataTask *_Nonnulltask, id _NullableresponseObject) {
dispatch_async(dispatch_get_main_queue(), ^{
//將子線程從網路拉取的資料用於主線程重新整理視圖
});
}failure:^(NSURLSessionDataTask*_Nullabletask,NSError*_Nonnullerror) {
}];
通過查看manager方法代碼可以看到其實現最終是調用了父類AFURLSessionManager的initWithSessionConfiguration:方法,該方法程式碼片段如下:
self.sessionConfiguration = configuration;
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
self.responseSerializer = [AFJSONResponseSerializer serializer];
可以看到AFNetworking對資料的解析方式預設是json解析。
這裡設定的代理操作隊列最大的並行作業數為1是讓所有請求的發起和等待網路響應均在同一條線程中執行,而不用為每一個請求都建立一條線程,這樣節約了很多資源。
在響應到達後會執行AFURLSessionManager的NSURLSessionDataDelegate協議的方法,[AFURLSessioinManager URLSession:dataTask:didReceiveData:]用於尋找對應的響應代理,並將後續的資料處理如資料拼接轉交給該代理。該方法的實現代碼如下:
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
[delegate URLSession:session dataTask:dataTask didReceiveData:data];
if (self.dataTaskDidReceiveData) {
self.dataTaskDidReceiveData(session, dataTask, data);
}
在資料比較大時,改方法可能會多次執行。
當資料轉送完成後會調用[AFURLSessioinManager URLSession:task:didCompleteWithError],該方法用於讓對應的代理執行NSURLSessionTaskDelegate協議中的方法,並將該代理對象從字典中移除,原始碼如下:
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
隨後代理執行URLSession:task:didCompleteWithError:,該方法把資料放到另一個由靜態方法產生的url_session_manager_processing_queue操作隊列中做資料解析,如json解析,並將解析後的資料回傳到主線程或者你自己產生的操作隊列裡,通過通知中樞將請求完成的訊息傳遞到主線程去(後面會寫一篇文章介紹通知中樞的實現原理,並寫一個類似的通知中樞)。該方法源碼片段如下:
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:[NSData dataWithData:self.mutableData] error:&serializationError];
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
整個流程可由表示:
所以NSURLSession的實現完全是借鑒了之前的AFNetworking,NSURLSession中維護了兩個操作隊列,一個用於處理髮起請求,等待響應,一個用於處理響應到達後需要執行的回調,對資料進行操作。如果是使用AFHTTPSessionManager的manager方法,初始化session的操作隊列的最大並發數為1,則與基於NSURLConnection的AFNetworking完全一樣,所有請求和等待響應都在一條線程中執行,然後資料的解析在一個非同步並發的操作隊列中執行,當然你可以設定該操作隊列的最大並發數。
AFNetworking源碼分析