簡述
在iOS開發中,與直接使用蘋果框架中提供的NSURLConnection或NSURLSession進行網路請求相比,使用AFNetworking會有哪些好處?當同時發起多個網路請求AFNetworking是如何實現併發的,在併發的時候,AFNetworking是如何管理執行緒的?蘋果重構NSURLConnetion推出新的網路載入系統NSURLSession解決了什麼問題或者是與NSURLConnection相比NSURLSession有好些好處?上面問題的答案會貫穿在這篇文章中(本篇文章只涉及了與操作佇列,多執行緒相關的分析)。
不用網路框架進行網路請求
NSURLConnection的簡單使用(下面的程式碼均只為了演示,更詳細的使用方法請自行谷歌)
NSURLConnection提供了兩個類方法用於發起同步或非同步請求,對於非同步請求來說必然是在子執行緒中發起,若在主執行緒中發起非同步網路請求會造成主執行緒阻塞,介面無響應,這就涉及到多執行緒程式設計。但多執行緒程式設計是一門非常細緻的活,要考慮很多的問題,比如執行緒的生命週期,多執行緒資源競爭,加鎖,避免死鎖,稍不留意就會踩到坑裡。好在蘋果的介面使用極其方便,你甚至不需要理解多執行緒就能輕鬆使用上多執行緒了,發起非同步請求的介面內部已經實現了多執行緒相關的操作。越是高階的介面,其隱藏的細節就越多,對於開發者來說當然是很方便,但若還能對其實現有所理解那就更完美了。當子執行緒發起了非同步請求後會阻塞以等待網路響應,那應該由誰來處理網路響應呢?蘋果提供了兩種方式,一種是block,提供一個處理響應的block回撥。一種是代理,使用代理的話就必須實現NSURLConnectionDelegate這個協議。你只需要在有網路請求的UIViewController中呼叫NSURLConnection提供的類方法就可以了。但如果你的專案中有不止一個UIViewController或者有的UIViewController中都不止一個請求的話,你就需要在每一個有網路請求的UIViewController中這樣寫。這樣寫會有什麼問題呢?首先會造成軟體結構不清晰,沒有剝離出網路層,其次沒有實現網路請求統一管理,無法實現取消所有網路請求等功能,再有會出現很多重複的程式碼,沒有讓公用功能形成模組,進行復用。當然為了解決這些問題你也可以自己實現自己的網路框架。
使用NSURLConnection版本的AFNetworking
使用網路框架的好處在於可以將分散在各個檢視控制器中的網路請求統一起來模組化形成網路層,降低與資料層和表現層的耦合。AFNetworking就做了這樣的工作。網路上已經有很多分析基於NSURLConnection實現的AFNetworking 2.x的原始碼,這裡只簡單說一下其實現整個流程,想要深入瞭解的請谷歌或檢視其原始碼。首先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文件):
1 2 3 4 5 6 7 8 |
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:方法,該方法程式碼片段如下:
1 2 3 4 5 |
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:]用於查詢對應的響應代理,並將後續的資料處理如資料拼接轉交給該代理。該方法的實現程式碼如下:
1 2 3 4 5 6 |
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協議中的方法,並將該代理物件從字典中移除,原始碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
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解析,並將解析後的資料回傳到主執行緒或者你自己生成的操作佇列裡,通過通知中心將請求完成的訊息傳遞到主執行緒去(後面會寫一篇文章介紹通知中心的實現原理,並寫一個類似的通知中心)。該方法原始碼片段如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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完全一樣,所有請求和等待響應都在一條執行緒中執行,然後資料的解析在一個非同步併發的操作佇列中執行,當然你可以設定該操作佇列的最大併發數。
總結
這是我寫的第一篇部落格,還有很多沒有考慮到地方,也侷限於自己的表述能力,文中可能有很多不通暢的地方,歡迎一起討論。