簡述
在iOS開發(fā)中,與直接使用蘋果框架中提供的NSURLConnection或NSURLSession進(jìn)行網(wǎng)絡(luò)請(qǐng)求相比橄登,使用AFNetworking會(huì)有哪些好處抓歼?當(dāng)同時(shí)發(fā)起多個(gè)網(wǎng)絡(luò)請(qǐng)求AFNetworking是如何實(shí)現(xiàn)并發(fā)的,在并發(fā)的時(shí)候拢锹,AFNetworking是如何管理線程的谣妻?蘋果重構(gòu)NSURLConnetion推出新的網(wǎng)絡(luò)加載系統(tǒng)NSURLSession解決了什么問題或者是與NSURLConnection相比NSURLSession有好些好處?上面問題的答案會(huì)貫穿在這篇文章中(本篇文章只涉及了與操作隊(duì)列卒稳,多線程相關(guān)的分析)蹋半。
不用網(wǎng)絡(luò)框架進(jìn)行網(wǎng)絡(luò)請(qǐng)求
NSURLConnection的簡單使用(下面的代碼均只為了演示,更詳細(xì)的使用方法請(qǐng)自行谷歌)
NSURLConnection提供了兩個(gè)類方法用于發(fā)起同步或異步請(qǐng)求充坑,對(duì)于異步請(qǐng)求來說必然是在子線程中發(fā)起减江,若在主線程中發(fā)起異步網(wǎng)絡(luò)請(qǐng)求會(huì)造成主線程阻塞,界面無響應(yīng)捻爷,這就涉及到多線程編程辈灼。但多線程編程是一門非常細(xì)致的活,要考慮很多的問題也榄,比如線程的生命周期巡莹,多線程資源競爭,加鎖甜紫,避免死鎖降宅,稍不留意就會(huì)踩到坑里。好在蘋果的接口使用極其方便囚霸,你甚至不需要理解多線程就能輕松使用上多線程了腰根,發(fā)起異步請(qǐng)求的接口內(nèi)部已經(jīng)實(shí)現(xiàn)了多線程相關(guān)的操作。越是高級(jí)的接口拓型,其隱藏的細(xì)節(jié)就越多额嘿,對(duì)于開發(fā)者來說當(dāng)然是很方便,但若還能對(duì)其實(shí)現(xiàn)有所理解那就更完美了吨述。當(dāng)子線程發(fā)起了異步請(qǐng)求后會(huì)阻塞以等待網(wǎng)絡(luò)響應(yīng)岩睁,那應(yīng)該由誰來處理網(wǎng)絡(luò)響應(yīng)呢钞脂?蘋果提供了兩種方式揣云,一種是block,提供一個(gè)處理響應(yīng)的block回調(diào)冰啃。一種是代理邓夕,使用代理的話就必須實(shí)現(xiàn)NSURLConnectionDelegate這個(gè)協(xié)議刘莹。你只需要在有網(wǎng)絡(luò)請(qǐng)求的UIViewController中調(diào)用NSURLConnection提供的類方法就可以了。但如果你的項(xiàng)目中有不止一個(gè)UIViewController或者有的UIViewController中都不止一個(gè)請(qǐng)求的話焚刚,你就需要在每一個(gè)有網(wǎng)絡(luò)請(qǐng)求的UIViewController中這樣寫点弯。這樣寫會(huì)有什么問題呢?首先會(huì)造成軟件結(jié)構(gòu)不清晰矿咕,沒有剝離出網(wǎng)絡(luò)層抢肛,其次沒有實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求統(tǒng)一管理,無法實(shí)現(xiàn)取消所有網(wǎng)絡(luò)請(qǐng)求等功能碳柱,再有會(huì)出現(xiàn)很多重復(fù)的代碼捡絮,沒有讓公用功能形成模塊,進(jìn)行復(fù)用莲镣。當(dāng)然為了解決這些問題你也可以自己實(shí)現(xiàn)自己的網(wǎng)絡(luò)框架福稳。
使用NSURLConnection版本的AFNetworking
使用網(wǎng)絡(luò)框架的好處在于可以將分散在各個(gè)視圖控制器中的網(wǎng)絡(luò)請(qǐng)求統(tǒng)一起來模塊化形成網(wǎng)絡(luò)層,降低與數(shù)據(jù)層和表現(xiàn)層的耦合瑞侮。AFNetworking就做了這樣的工作的圆。網(wǎng)絡(luò)上已經(jīng)有很多分析基于NSURLConnection實(shí)現(xiàn)的AFNetworking 2.x的源代碼,這里只簡單說一下其實(shí)現(xiàn)整個(gè)流程半火,想要深入了解的請(qǐng)谷歌或查看其源代碼越妈。首先AFNetworking要解決實(shí)現(xiàn)接口統(tǒng)一和所有網(wǎng)絡(luò)請(qǐng)求統(tǒng)一管理的問題。NSURLConnection發(fā)起有兩種方式發(fā)起請(qǐng)求慈缔,分別是設(shè)置響應(yīng)block和代理叮称,那采用哪種方式能夠?qū)崿F(xiàn)網(wǎng)絡(luò)請(qǐng)求統(tǒng)一管理呢?block肯定不行藐鹤,因?yàn)閭魅腠憫?yīng)回調(diào)block的[NSURLConnection sendAsynchronousRequest:queue:completionHandler:]方法會(huì)立即執(zhí)行瓤檐。那么只能使用設(shè)置響應(yīng)代理這種方式了。具體實(shí)現(xiàn)是將網(wǎng)絡(luò)請(qǐng)求繼承于NSOperation娱节,生產(chǎn)網(wǎng)絡(luò)請(qǐng)求挠蛉,然后再將這個(gè)網(wǎng)絡(luò)請(qǐng)求加入操作隊(duì)列里,剩下的所有與線程相關(guān)的操作都由這個(gè)操作隊(duì)列去實(shí)現(xiàn)肄满。越高級(jí)易容的接口就隱藏越多的實(shí)現(xiàn)細(xì)節(jié)谴古,NSOperation隱藏了線程相關(guān)的所有細(xì)節(jié),使得開發(fā)者只需關(guān)心構(gòu)建什么樣的操作稠歉。AFNetworking甚至隱藏了操作掰担,操作隊(duì)列的概念,使得開發(fā)者只需關(guān)心如何設(shè)置網(wǎng)絡(luò)相關(guān)的請(qǐng)求參數(shù)怒炸,響應(yīng)回調(diào)等带饱,而不用再關(guān)心當(dāng)多個(gè)網(wǎng)絡(luò)請(qǐng)求如何進(jìn)行統(tǒng)一管理,這些都已經(jīng)由AFNetworking內(nèi)部的操作隊(duì)列屬性完成了。那需要為每個(gè)請(qǐng)求創(chuàng)建一個(gè)線程來發(fā)起請(qǐng)求嗎勺疼?基于NSURLConnection的AFNetworking是只創(chuàng)建了一條線程來發(fā)起所有請(qǐng)求并阻塞以等待響應(yīng)教寂。
重構(gòu)推出的NSURLSession解決了NSURLConnection哪些問題
從2013年蘋果發(fā)布重構(gòu)后的加載系統(tǒng)NSURLSession,AFNetworking這個(gè)最受歡迎的網(wǎng)絡(luò)框架也隨之發(fā)布了基于NSURLSession的實(shí)現(xiàn)版本执庐。這篇文章從 NSURLConnection 到 NSURLSession對(duì)NSURLSession的使用做了介紹酪耕。既然是對(duì)NSURLConnection進(jìn)行的重構(gòu),那一定是解決了NSURLConnection存在的一些問題轨淌。蘋果的接口以及其簡單的形式呈現(xiàn)給開發(fā)者迂烁,盡量把復(fù)雜,容易出錯(cuò)的地方以簡單的接口暴露出來递鹉,這使得用戶能夠參考文檔很快上手婚被,但這也有一些弊端,就是開發(fā)者對(duì)其中的原理不是很了解梳虽。越高級(jí)的接口越容易使用址芯,但其隱蔽的實(shí)現(xiàn)細(xì)節(jié)就越多。相信在進(jìn)行多線程編程的時(shí)候窜觉,很多開發(fā)者遇到過各種各樣的坑谷炸,但在iOS平臺(tái)上,蘋果推出了GCD禀挫,NSperation等一系列接口可以讓開發(fā)者完全可以只關(guān)注業(yè)務(wù)的實(shí)現(xiàn)旬陡,這些接口內(nèi)部已經(jīng)替開發(fā)者管理好了線程的創(chuàng)建,銷毀语婴,多線程資源競爭等需要開發(fā)者費(fèi)很多精力的事情描孟,甚至開發(fā)者不用對(duì)多線程理解透徹都能把多線程用得得心應(yīng)手。NSURLConnection已經(jīng)隱藏了線程相關(guān)的操作砰左,已經(jīng)給開發(fā)者減輕了很多負(fù)擔(dān)匿醒。但NSURLConnectoin只隱藏了單個(gè)網(wǎng)絡(luò)請(qǐng)求的線程的相關(guān)操作,并沒有提供接口來解決多個(gè)網(wǎng)絡(luò)請(qǐng)求時(shí)多個(gè)線程的管理問題缠导,譬如當(dāng)有多個(gè)網(wǎng)絡(luò)請(qǐng)求時(shí)是否應(yīng)該使用線程池來避免不停創(chuàng)建與銷毀線程(這個(gè)可以有NSOperationQueue很好的解決)廉羔。并且NSURLConnection不是基于HTTP/2協(xié)議的,若使用NSURLConnection發(fā)起請(qǐng)求則每次請(qǐng)求都需要經(jīng)過三次握手過程僻造,可見NSURLConnection確實(shí)有很多可以優(yōu)化的地方(我只發(fā)現(xiàn)這些)憋他。NSURLConnection存在的無法將多個(gè)請(qǐng)求關(guān)聯(lián)起來的問題已經(jīng)很好的由AFNetworking解決了,所以推出的NSURLSession可以說是借鑒了AFNetworking的思想髓削,并且可以從AFNetworking的源代碼中很容易的看出來竹挡。基于NSURLConnection的AFNetworking需要讓網(wǎng)絡(luò)請(qǐng)求繼承與NSOperation立膛,然后再將該生成的網(wǎng)絡(luò)請(qǐng)求加入操作隊(duì)列中揪罕,但基于NSURLSessioin的AFNetworking只需要?jiǎng)?chuàng)建一個(gè)網(wǎng)絡(luò)請(qǐng)求任務(wù)就可以了,原因在于,NSURLSession內(nèi)部已經(jīng)維護(hù)了兩個(gè)操作隊(duì)列耸序,一個(gè)是處理session的相關(guān)回調(diào),一個(gè)是處理響應(yīng)相關(guān)的回調(diào)鲁猩,所以說NSURLSession是借鑒了AFNetworking的繼承于NSOperation和用單線程發(fā)起并等待響應(yīng)的思想(這個(gè)會(huì)在后面給出證明)坎怪。
基于NSURLSession的AFNetworking的源碼分析
下圖是基于NSURLSession的AFNetworking的UML圖(只為展示類之間的關(guān)聯(lián)關(guān)系,并沒有給出每個(gè)類的所有屬性和方法):
從該類圖已經(jīng)能夠明白AFNetworking整個(gè)的工作流程廓握。
下面進(jìn)行代碼分析搅窿,使用AFHTTPSessionManager進(jìn)行網(wǎng)絡(luò)請(qǐng)求的示例代碼(具體的使用請(qǐng)參考AFNetworking的README文檔):
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:URL parameters:nil progress:nil success:^(NSURLSessionDataTask *_Nonnulltask, id _NullableresponseObject) {
dispatch_async(dispatch_get_main_queue(), ^{
//將子線程從網(wǎng)絡(luò)拉取的數(shù)據(jù)用于主線程刷新視圖
});
}failure:^(NSURLSessionDataTask*_Nullabletask,NSError*_Nonnullerror) {
}];
通過查看manager方法代碼可以看到其實(shí)現(xiàn)最終是調(diào)用了父類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對(duì)數(shù)據(jù)的解析方式默認(rèn)是json解析隙券。
這里設(shè)置的代理操作隊(duì)列最大的并發(fā)操作數(shù)為1是讓所有請(qǐng)求的發(fā)起和等待網(wǎng)絡(luò)響應(yīng)均在同一條線程中執(zhí)行男应,而不用為每一個(gè)請(qǐng)求都新建一條線程,這樣節(jié)約了很多資源娱仔。
在響應(yīng)到達(dá)后會(huì)執(zhí)行AFURLSessionManager的NSURLSessionDataDelegate協(xié)議的方法沐飘,[AFURLSessioinManager URLSession:dataTask:didReceiveData:]用于查找對(duì)應(yīng)的響應(yīng)代理,并將后續(xù)的數(shù)據(jù)處理如數(shù)據(jù)拼接轉(zhuǎn)交給該代理牲迫。該方法的實(shí)現(xiàn)代碼如下:
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
[delegate URLSession:session dataTask:dataTask didReceiveData:data];
if (self.dataTaskDidReceiveData) {
self.dataTaskDidReceiveData(session, dataTask, data);
}
在數(shù)據(jù)比較大時(shí)耐朴,改方法可能會(huì)多次執(zhí)行。
當(dāng)數(shù)據(jù)傳輸完成后會(huì)調(diào)用[AFURLSessioinManager URLSession:task:didCompleteWithError],該方法用于讓對(duì)應(yīng)的代理執(zhí)行NSURLSessionTaskDelegate協(xié)議中的方法盹憎,并將該代理對(duì)象從字典中移除筛峭,源代碼如下:
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);
}
隨后代理執(zhí)行URLSession:task:didCompleteWithError:,該方法把數(shù)據(jù)放到另一個(gè)由靜態(tài)方法生成的url_session_manager_processing_queue操作隊(duì)列中做數(shù)據(jù)解析陪每,如json解析影晓,并將解析后的數(shù)據(jù)回傳到主線程或者你自己生成的操作隊(duì)列里,通過通知中心將請(qǐng)求完成的消息傳遞到主線程去(后面會(huì)寫一篇文章介紹通知中心的實(shí)現(xiàn)原理,并寫一個(gè)類似的通知中心)檩禾。該方法源碼片段如下:
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];
});
});
});
整個(gè)流程可由下圖表示:
所以NSURLSession的實(shí)現(xiàn)完全是借鑒了之前的AFNetworking挂签,NSURLSession中維護(hù)了兩個(gè)操作隊(duì)列,一個(gè)用于處理發(fā)起請(qǐng)求盼产,等待響應(yīng)竹握,一個(gè)用于處理響應(yīng)到達(dá)后需要執(zhí)行的回調(diào),對(duì)數(shù)據(jù)進(jìn)行操作辆飘。如果是使用AFHTTPSessionManager的manager方法啦辐,初始化session的操作隊(duì)列的最大并發(fā)數(shù)為1,則與基于NSURLConnection的AFNetworking完全一樣蜈项,所有請(qǐng)求和等待響應(yīng)都在一條線程中執(zhí)行芹关,然后數(shù)據(jù)的解析在一個(gè)異步并發(fā)的操作隊(duì)列中執(zhí)行,當(dāng)然你可以設(shè)置該操作隊(duì)列的最大并發(fā)數(shù)紧卒。
總結(jié)
局限于自己的表述能力侥衬,文中可能有很多不通暢的地方,歡迎一起討論。
歡迎關(guān)注我的簡書轴总,我會(huì)定期做一些技術(shù)分享:)