iOS應用架構談 網(wǎng)絡層設計方案
前言
網(wǎng)絡層在一個App
中也是一個不可缺少的部分,工程師們在網(wǎng)絡層能夠發(fā)揮的空間也比較大缆瓣。另外佑钾,蘋果對網(wǎng)絡請求部分已經(jīng)做了很好的封裝咕宿,業(yè)界的AFNetworking
也被廣泛使用。其它的ASIHttpRequest
蜒简,MKNetworkKit
啥的其實也都還不錯瘸羡,但前者已經(jīng)棄坑,后者也在棄坑的邊緣臭蚁。在實際的App
開發(fā)中最铁,Afnetworking
已經(jīng)成為了事實上各大App的標準配置。
網(wǎng)絡層在一個App中承載了API調用垮兑,用戶操作日志記錄冷尉,甚至是即時通訊等任務。我接觸過一些App(開源的和不開源的)的代碼系枪,在看到網(wǎng)絡層這一塊時雀哨,尤其是在看到各位架構師各顯神通展示了各種技巧,我非常為之感到興奮。但有的時候雾棺,往往也對于其中的一些缺陷感到失望膊夹。
關于網(wǎng)絡層的設計方案會有很多,需要權衡的地方也會有很多捌浩,甚至于爭議的地方都會有很多放刨。但無論如何,我都不會對這些問題做出任何逃避尸饺,我會在這篇文章中給出我對它們的看法和解決方案进统,觀點絕不中立,不會跟大家打太極浪听。
這篇文章就主要會講這些方面:
- 網(wǎng)絡層跟業(yè)務對接部分的設計
- 網(wǎng)絡層的安全機制實現(xiàn)
- 網(wǎng)絡層的優(yōu)化方案
網(wǎng)絡層跟業(yè)務對接部分的設計
在安居客App的架構更新?lián)Q代的時候螟碎,我深深地感覺到網(wǎng)絡層跟業(yè)務對接部分的設計有多么重要,因此我對它做的最大改變就是針對網(wǎng)絡層跟業(yè)務對接部分的改變迹栓。網(wǎng)絡層跟業(yè)務層對接部分設計的好壞掉分,會直接影響到業(yè)務工程師實現(xiàn)功能時的心情。
在正式開始講設計之前克伊,我們要先討論幾個問題:
- 使用哪種交互模式來跟業(yè)務層做對接酥郭?
- 是否有必要將API返回的數(shù)據(jù)封裝成對象然后再交付給業(yè)務層?
- 使用集約化調用方式還是離散型調用方式去調用API答毫?
這些問題討論完畢之后褥民,我會給出一個完整的設計方案來給大家做參考,設計方案是魚洗搂,討論的這些問題是漁消返,我什么都授了,大家各取所需耘拇。
使用哪種交互模式來跟業(yè)務層做對接撵颊?
這里其實有兩個問題:
- 以什么方式將數(shù)據(jù)交付給業(yè)務層?
- 交付什么樣的數(shù)據(jù)給業(yè)務層惫叛?
以什么方式將數(shù)據(jù)交付給業(yè)務層倡勇?
iOS開發(fā)領域有很多對象間數(shù)據(jù)的傳遞方式,我看到的大多數(shù)App在網(wǎng)絡層所采用的方案主要集中于這三種:Delegate嘉涌,Notification妻熊,Block。KVO和Target-Action
我目前還沒有看到有使用的仑最。
目前我知道邊鋒主要是采用的block
扔役,大智慧主要采用的是Notification
,安居客早期以Block
為主警医,后面改成了以Delegate
為主亿胸,阿里沒發(fā)現(xiàn)有通過Notification
來做數(shù)據(jù)傳遞的地方(可能有)坯钦,Delegate、Block以及target-action
都有侈玄,阿里iOS App
網(wǎng)絡層的作者說這是為了方便業(yè)務層選擇自己合適的方法去使用婉刀。這里大家都是各顯神通,每次我看到這部分的時候序仙,我都喜歡問作者為什么采用這種交互方案突颊,但很少有作者能夠說出個條條框框來。
然而在我這邊潘悼,我的意見是以Delegate
為主洋丐,Notification
為輔。原因如下:
- 盡可能減少跨層數(shù)據(jù)交流的可能挥等,限制耦合
- 統(tǒng)一回調方法,便于調試和維護
- 在跟業(yè)務層對接的部分只采用一種對接手段(在我這兒就是只采用delegate這一個手段)限制靈活性堤尾,以此來交換應用的可維護性
盡可能減少跨層數(shù)據(jù)交流的可能肝劲,限制耦合
什么叫跨層數(shù)據(jù)交流?就是某一層(或模塊)跟另外的與之沒有直接對接關系的層(或模塊)產(chǎn)生了數(shù)據(jù)交換郭宝。為什么這種情況不好辞槐?嚴格來說應該是大部分情況都不好,有的時候跨層數(shù)據(jù)交流確實也是一種需求粘室。之所以說不好的地方在于榄檬,它會導致代碼混亂,破壞模塊的封裝性衔统。我們在做分層架構的目的其中之一就在于下層對上層有一次抽象鹿榜,讓上層可以不必關心下層細節(jié)而執(zhí)行自己的業(yè)務。
所以锦爵,如果下層細節(jié)被跨層暴露舱殿,一方面你很容易因此失去鄰層對這個暴露細節(jié)的保護;另一方面险掀,你又不可能不去處理這個細節(jié)沪袭,所以處理細節(jié)的相關代碼就會散落各地,最終難以維護樟氢。
說得具象一點就是冈绊,我們考慮這樣一種情況:A<-B<-C
。當C有什么事件埠啃,通過某種方式告知B死宣,然后B執(zhí)行相應的邏輯。一旦告知方式不合理霸妹,讓A有了跨層知道C的事件的可能十电,你 就很難保證A層業(yè)務工程師在將來不會對這個細節(jié)作處理。一旦業(yè)務工程師在A層產(chǎn)生處理操作,有可能是補充邏輯鹃骂,也有可能是執(zhí)行業(yè)務台盯,那么這個細節(jié)的相關處理代碼就會有一部分散落在A層。然而前者是不應該散落在A層的畏线,后者有可能是需求静盅。另外,因為B層是對A層抽象的寝殴,執(zhí)行補充邏輯的時候蒿叠,有可能和B層針對這個事件的處理邏輯產(chǎn)生沖突,這是我們很不希望看到的蚣常。
那么什么情況跨層數(shù)據(jù)交流會成為需求市咽?在網(wǎng)絡層這邊,信號從2G變成3G變成4G變成Wi-Fi抵蚊,這個是跨層數(shù)據(jù)交流的其中一個需求施绎。不過其他的跨層數(shù)據(jù)交流需求我暫時也想不到了,哈哈贞绳,應該也就這一個吧谷醉。
嚴格來說,使用Notification
來進行網(wǎng)絡層和業(yè)務層之間數(shù)據(jù)的交換冈闭,并不代表這一定就是跨層數(shù)據(jù)交流俱尼,但是使用Notification
給跨層數(shù)據(jù)交流開了一道口子,因為Notification
的影響面不可控制萎攒,只要存在實例就存在被影響的可能遇八。另外,這也會導致誰都不能保證相關處理代碼就在唯一的那個地方耍休,進而帶來維護災難押蚤。作為架構師,在這里給業(yè)務工程師限制其操作的靈活性是必要的羹应。另外揽碘,Notification
也支持一對多的情況,這也給代碼散落提供了條件园匹。同時雳刺,Notification
所對應的響應方法很難在編譯層面作限制,不同的業(yè)務工程師會給他取不同的名字裸违,這也會給代碼的可維護性帶來災難掖桦。
手機淘寶架構組的俠武同學曾經(jīng)給我分享過一個問題,在這里我也分享給大家:曾經(jīng)有一個工程師在監(jiān)聽Notification之后供汛,沒有寫釋放監(jiān)聽的代碼枪汪,當然涌穆,找到這個原因又是很漫長的一段故事,現(xiàn)在找到原因了雀久,然而監(jiān)聽這個Notification的對象有那么多宿稀,不知道具體是哪個Notificaiton
,也不知道那個沒釋放監(jiān)聽的對象是誰赖捌。后來折騰了很久大家都沒辦法的時候祝沸,有一個經(jīng)驗豐富的工程師提出用hook(Method Swizzling)的方式,最終找到了那個沒釋放監(jiān)聽的對象越庇,bug修復了罩锐。
我分享這個問題的目的并不是想強調Notification
多么多么不好,Notification
本身就是一種設計模式卤唉,在屬于他的問題領域內(nèi)涩惑,Notification
是非常好的一種解決方案。但我想強調的是桑驱,對于網(wǎng)絡層這個問題領域內(nèi)來看境氢,架構師首先一定要限制代碼的影響范圍,在能用影響范圍小的方案的時候就盡量采用這種小的方案碰纬,否則將來要是有什么奇怪需求或者出了什么小問題,維護起來就非常麻煩问芬。因此Notification
這個方案不能作為首選方案悦析,只能作為備選。
那么Notification
也不是完全不能使用此衅,當需求要求跨層時强戴,我們就可以使用Notification
,比如前面提到的網(wǎng)絡條件切換挡鞍,而且這個需求也是需要滿足一對多的骑歹。
所以,為了符合前面所說的這些要求墨微,使用Delegate能夠很好地避免跨層訪問道媚,同時限制了響應代碼的形式,相比Notification
而言有更好的可維護性翘县。
然后我們順便來說說為什么盡量不要用block最域。
block很難追蹤,難以維護
我們在調試的時候經(jīng)常會單步追蹤到某一個地方之后锈麸,發(fā)現(xiàn)尼瑪這里有個block
镀脂,如果想知道這個block
里面都做了些什么事情,這時候就比較蛋疼了忘伞。
- (void)someFunctionWithBlock:(SomeBlock *)block
{
... ...
-> block(); //當你單步走到這兒的時候薄翅,要想知道block里面都做了哪些事情的話沙兰,就很麻煩。
... ...
}
block會延長相關對象的生命周期
block
會給內(nèi)部所有的對象引用計數(shù)加一翘魄,這一方面會帶來潛在的retain cycle
鼎天,不過我們可以通過Weak Self
的手段解決。另一方面比較重要就是熟丸,它會延長對象的生命周期训措。
在網(wǎng)絡回調中使用block
,是block
導致對象生命周期被延長的其中一個場合光羞,當ViewController
從window
中卸下時绩鸣,如果尚有請求帶著block
在外面飛,然后block里面引用了ViewController
(這種場合非常常見)纱兑,那么ViewController
是不能被及時回收的呀闻,即便你已經(jīng)取消了請求,那也還是必須得等到請求著陸之后才能被回收潜慎。
然而使用delegate
就不會有這樣的問題捡多,delegate
是弱引用,哪怕請求仍然在外面飛铐炫,垒手,ViewController
還是能夠及時被回收的,回收之后指針自動被置為了nil
倒信,無傷大雅科贬。
block在離散型場景下不符合使用的規(guī)范
block
和delegate
乍看上去在作用上是很相似,但是關于它們的選型有一條嚴格的規(guī)范:當回調之后要做的任務在每次回調時都是一致的情況下鳖悠,選擇delegate
榜掌,在回調之后要做的任務在每次回調時無法保證一致,選擇block乘综。在離散型調用的場景下憎账,每一次回調都是能夠保證任務一致的,因此適用delegate
卡辰。這也是蘋果原生的網(wǎng)絡調用也采用delegate
的原因胞皱,因為蘋果也是基于離散模型去設計網(wǎng)絡調用的,而且本文即將要介紹的網(wǎng)絡層架構也是基于離散型調用的思路去設計的九妈。
在集約型調用的場景下朴恳,使用block
是合理的,因為每次請求的類型都不一樣允蚣,那么自然回調要做的任務也都會不一樣于颖,因此只能采用block
。AFNetworking
就是屬于集約型調用嚷兔,因此它采用了block
來做回調森渐。
就我所知做入,目前大部分公司的App網(wǎng)絡層都是集約型調用,因此廣泛采取了block
回調同衣。但是在App的網(wǎng)絡層架構設計中直接采用集約型調用來為業(yè)務服務的思路是有問題的竟块,因此在遷移到離散型調用時,一定要注意這一點耐齐,記得遷回delegate
回調浪秘。關于離散型和集約型調用的介紹和如何選型,我在后面的集約型API調用方式和離散型API調用方式的選擇埠况?小節(jié)中有詳細的介紹耸携。
所以平時盡量不要濫用block
,尤其是在網(wǎng)絡層這里辕翰。
統(tǒng)一回調方法夺衍,便于調試和維護
前面講的是跨層問題,區(qū)分了Delegate
和Notification
喜命,順帶談了一下Block
沟沙。然后現(xiàn)在談到的這個情況,就是另一個采用Block
方案不是很合適的情況壁榕。首先矛紫,Block
本身無好壞對錯之分,只有合適不合適牌里。在這一節(jié)要講的情況里颊咬,Block
無法做到回調方法的統(tǒng)一,調試和維護的時候也很難在調用棧上顯示出來二庵,找的時候會很蛋疼。
在網(wǎng)絡請求和網(wǎng)絡層接受請求的地方時缓呛,使用Block
沒問題催享。但是在獲得數(shù)據(jù)交給業(yè)務方時,最好還是通過Delegate去通知到業(yè)務方哟绊。因為Block
所包含的回調代碼跟調用邏輯放在同一個地方因妙,會導致那部分代碼變得很長,因為這里面包括了調用前和調用后的邏輯票髓。從另一個角度說攀涵,這在一定程度上違背了single function,single task
的原則洽沟,在需要調用API的地方,就只要寫API調用相關的代碼,在回調的地方送丰,寫回調的代碼。
然后我看到大部分App里炉媒,當業(yè)務工程師寫代碼寫到這邊的時候昆烁,也意識到了這個問題静尼。因此他們會在block里面寫個一句話的方法接收參數(shù),然后做轉發(fā)鸭巴,然后就可以把這個方法放在其他地方了奕扣,繞過了Block
的回調著陸點不統(tǒng)一的情況掌敬。比如這樣:
[API callApiWithParam:param successed:^(Response *response){
[self successedWithResponse:response];
} failed:^(Request *request, NSError *error){
[self failedWithRequest:request error:error];
}];