AFNetworking源碼分析

簡述

在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è)類的所有屬性和方法):

AFNetworking.png

從該類圖已經(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è)流程可由下圖表示:

流程圖.png

所以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ù)分享:)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末直颅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子怀樟,更是在濱河造成了極大的恐慌功偿,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件往堡,死亡現(xiàn)場離奇詭異械荷,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)虑灰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門吨瞎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人穆咐,你說我怎么就攤上這事颤诀。” “怎么了对湃?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵着绊,是天一觀的道長。 經(jīng)常有香客問我熟尉,道長归露,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任斤儿,我火速辦了婚禮剧包,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘往果。我一直安慰自己疆液,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布陕贮。 她就那樣靜靜地躺著堕油,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肮之。 梳的紋絲不亂的頭發(fā)上掉缺,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音戈擒,去河邊找鬼眶明。 笑死,一個(gè)胖子當(dāng)著我的面吹牛筐高,可吹牛的內(nèi)容都是我干的搜囱。 我是一名探鬼主播丑瞧,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼蜀肘!你這毒婦竟也來了绊汹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤扮宠,失蹤者是張志新(化名)和其女友劉穎西乖,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涵卵,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年荒叼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了轿偎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡被廓,死狀恐怖坏晦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嫁乘,我是刑警寧澤昆婿,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站蜓斧,受9級(jí)特大地震影響仓蛆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜挎春,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一看疙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧直奋,春花似錦能庆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至邮绿,卻和暖如春渠旁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背船逮。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工一死, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人傻唾。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓投慈,卻偏偏與公主長得像承耿,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子伪煤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 說到AFNetwokring這個(gè)強(qiáng)大第三方網(wǎng)絡(luò)請(qǐng)求庫加袋,大家應(yīng)該都不陌生吧,ios開發(fā)抱既、mac開發(fā)都經(jīng)常用职烧,主要是他...
    塵峰的小孩閱讀 477評(píng)論 0 0
  • AF版本基于3.0,下面將從使用切入開始分析。 1.使用 例子: 2 代碼分析 2.1 [AFHTTPSessio...
    Arnold134777閱讀 1,428評(píng)論 0 2
  • AFNetworking作為iOS開發(fā)中最常用的網(wǎng)絡(luò)請(qǐng)求框架之一防泵,其內(nèi)部實(shí)現(xiàn)究竟是怎么一步一步實(shí)現(xiàn)的呢蚀之?現(xiàn)在我們來...
    mrChan1234閱讀 338評(píng)論 0 0
  • AFNetworking 實(shí)現(xiàn)了文件的上傳/下載,以及斷點(diǎn)續(xù)傳捷泞。內(nèi)部封裝了 NSURLSession ,替代了之前...
    sea777777閱讀 716評(píng)論 0 0
  • 這是一篇命題作文足删,是我的學(xué)生留言要我寫的,TA要我寫的是留在大城市锁右,想了想失受,北上廣沒呆過,還是現(xiàn)實(shí)點(diǎn)咏瑟,寫留在深圳拂到。...
    太后老三閱讀 2,687評(píng)論 15 14