談?wù)?iOS 網(wǎng)絡(luò)層設(shè)計(jì)

對(duì)于 CTNetworking 設(shè)計(jì)理念和筆者的理解,Casa Taloyum 給出了回復(fù):

  1. 已發(fā)出的請(qǐng)求是不可能做到真正取消的,所以請(qǐng)求的取消在實(shí)現(xiàn)上就是“即使拿到數(shù)據(jù)也不回調(diào)給業(yè)務(wù)”。這個(gè)在CTNetworking里面是已經(jīng)做好了的。
  2. Service的概念是用于封裝第三方SDK的,例如我在Github上給到的Marvel API SDK和高德地圖API SDK。一組API中某個(gè)API不規(guī)范的情況,是可以在Service的實(shí)現(xiàn)中或者APIManager的視線中給予適配的。這也就是為什么Service只是一個(gè)protocol而不是一個(gè)具體實(shí)現(xiàn)的原因。所以protocol方式定義的service不是缺點(diǎn),它是功能。
  3. CTNetworking已經(jīng)足夠成熟了。我有一個(gè)目標(biāo)是將所有的第三方API都以CTNetworking的方式封裝,而完成這一目標(biāo)的所有基礎(chǔ)設(shè)施也都已經(jīng)完善了,所以CTNetworking在朝一個(gè)生態(tài)的方向去發(fā)展。具體可以看我給到的那些示范工程:Marvel API SDK、高德地圖API SDK。

CTNetworking的基礎(chǔ)設(shè)施包括

  1. bash的代碼自動(dòng)生成腳本,可以提高工程師在離散型API架構(gòu)下的工作效率。2. 基于CTMediator的配置管理,可以做到跨APP時(shí)的代碼復(fù)用,例如Marvel key在不同app上可能會(huì)有不同的key
  2. 用于實(shí)現(xiàn)API DEMO ViewController的父類,它可以極大地便于將API工程以APP的形式給用QA去做測(cè)試,給開(kāi)發(fā)去做調(diào)試或者當(dāng)API文檔用。
  3. CTJsbridge已經(jīng)可以跟CTNetworking交互,H5工程師可以很方便地使用基于CTNetworking的網(wǎng)絡(luò)API。

前言

基于 AFNetworking 的二次封裝網(wǎng)上蠻多的,比較好一點(diǎn)的就是 CTNetworking 和 YTKNetwork,但是看了一下源碼過(guò)后發(fā)現(xiàn)都有一些不足的地方,或者說(shuō)不太能滿足我們的業(yè)務(wù)需求??紤]到 AFNetworking 本身就為網(wǎng)絡(luò)層做了很多事情,二次封裝并非是個(gè)復(fù)雜的事情,所以索性自己寫(xiě)了個(gè)便于拓展和維護(hù) (代碼完全脫敏):

代碼地址和用法 : YBNetwork

參考思路:iOS應(yīng)用架構(gòu)談 網(wǎng)絡(luò)層設(shè)計(jì)方案
參考源碼:YTKNetwork CTNetworking

調(diào)研

Casa Taloyum 前輩的文章對(duì)筆者的架構(gòu)思維有著深遠(yuǎn)的影響,記得兩年多前入行不久,看得一知半解,近些時(shí)間要做架構(gòu)方面的工作,又去重溫了一下。

如何設(shè)計(jì)一個(gè)好的網(wǎng)絡(luò)層架構(gòu),在 Casa Taloyum 的文章中已經(jīng)說(shuō)得比較全面了。猿題庫(kù)的 YTKNetwork 相對(duì)比較成熟,兩份代碼核心思想都是將代碼歸為集約處理部分和離散處理部分,在實(shí)現(xiàn)方式上有些差別。

沒(méi)有什么技術(shù)難點(diǎn),直接看了一遍兩份開(kāi)源代碼,優(yōu)點(diǎn)很多,這里羅列一下不足的地方(當(dāng)然只是個(gè)人理解,并且筆者可能更多結(jié)合業(yè)務(wù)來(lái)考慮的):

CTNetworking 不足:

  • 使用 IOP 方式建立模塊,化繼承為組合。獨(dú)立<CTServiceProtocol><CTAPIManagerInterceptor>等協(xié)議作為集約管理部分,若個(gè)別接口需要修改這些公共配置,只能在集約管理模塊來(lái)判斷,顯得有一點(diǎn)繁瑣。
  • 記錄了一個(gè) request 實(shí)例的所有 task,在 dealloc 中自動(dòng)取消掉還未降落的網(wǎng)絡(luò)請(qǐng)求,但是實(shí)際上網(wǎng)絡(luò)請(qǐng)求任務(wù)會(huì)持有 request,所以自動(dòng)取消策略不成立了。

YTKNetwork 不足:

  • 基于多態(tài)的設(shè)計(jì)思路,提供了很多供重載的方法,從設(shè)計(jì)來(lái)看,框架是可以實(shí)例化YTKBaseRequest子類 直接使用的,那么直接使用時(shí)無(wú)法重載這些方法專門定制(個(gè)人看來(lái)有些地方使用屬性更靈活);并且,當(dāng)一個(gè) reqeust 多次start發(fā)起請(qǐng)求就會(huì)調(diào)用多次這些重載方法,可能造成多余計(jì)算;
  • 緩存策略使用一個(gè)YTKBaseRequest的子類YTKRequest來(lái)做,雖然這樣看起來(lái)比較優(yōu)雅,父類和子類各司其職,單一職責(zé),但是緩存策略難免會(huì)更改父類的邏輯,如此就很難不違背開(kāi)閉原則??蚣艿木彺嬷挥幸粋€(gè)失效時(shí)間控制,筆者想要拓展時(shí)發(fā)現(xiàn)要改的東西太多。
  • 同一個(gè) request 實(shí)例多次 start 調(diào)用網(wǎng)絡(luò)請(qǐng)求時(shí) (多個(gè)網(wǎng)絡(luò)請(qǐng)求并發(fā)情況),并未作出實(shí)際的處理策略,僅保留最新的NSURLSessionTask,而對(duì)舊的未結(jié)束的所有NSURLSessionTask喪失了控制權(quán)。
  • 網(wǎng)絡(luò)請(qǐng)求任務(wù)強(qiáng)持有所有 request 對(duì)象,在弱網(wǎng)環(huán)境下可能會(huì)有大量 request 對(duì)象無(wú)法釋放,而界面降落點(diǎn)可能不存在了。

共同不足:

  • 數(shù)據(jù)回調(diào)都是綁定在 request 上的,既然都未處理一個(gè) request 重復(fù)并發(fā)請(qǐng)求的情況,那么多個(gè)網(wǎng)絡(luò)請(qǐng)求落地時(shí),request 上的數(shù)據(jù)會(huì)突變,業(yè)務(wù)方的處理方式是不可控的,既有可能在回調(diào)業(yè)務(wù)執(zhí)行過(guò)程中發(fā)現(xiàn)數(shù)據(jù)變化了。

實(shí)際上針對(duì)團(tuán)隊(duì)的業(yè)務(wù),架構(gòu)上會(huì)有取舍,所以筆者列這些不足也可以說(shuō)是比較片面的。

實(shí)現(xiàn)

如何進(jìn)行離散請(qǐng)求調(diào)用?

在一個(gè)網(wǎng)絡(luò)請(qǐng)求起飛到降落過(guò)程中,有一系列獨(dú)有的配置始終能代表這一個(gè)網(wǎng)絡(luò)請(qǐng)求。

那么思路就出來(lái)了,只要把一個(gè)針對(duì)某個(gè)接口的配置對(duì)象傳遞過(guò)去,讓網(wǎng)絡(luò)任務(wù)的閉包持有這個(gè)對(duì)象,然后在網(wǎng)絡(luò)回調(diào)處理中,一直傳遞這個(gè)配置對(duì)象,像踢皮球一樣,最終處理好后回調(diào)到業(yè)務(wù)類中。

怎么避免這個(gè)配置對(duì)象瘋狂傳遞?實(shí)際上就可以把網(wǎng)絡(luò)回調(diào)處理邏輯,放在這個(gè)配置對(duì)象中,就像CTNetworkingCTAPIBaseManager配置對(duì)象,只要安全落地就能命中對(duì)應(yīng)的配置對(duì)象;也可以用一個(gè)全局容器把這些配置對(duì)象裝起來(lái),不用一直通過(guò)閉包傳遞,就像YTKNetworkYTKBaseRequest配置對(duì)象。

所以筆者之前用了一個(gè)奇怪的思路:

Config config = Config.new;
[NetworkManager startWithConfig:config success:^{} failure:^{}];

實(shí)際上這和上面兩個(gè)框架道理是一樣的,筆者內(nèi)部也會(huì)寫(xiě)邏輯去管理所有config,但是這么做不好對(duì)單獨(dú)的網(wǎng)絡(luò)請(qǐng)求進(jìn)行管理,非要管理的話,又需要去持有這個(gè)config了。

實(shí)現(xiàn)代碼類:

  • YBNetworkManager : 負(fù)責(zé)組織數(shù)據(jù)發(fā)起網(wǎng)絡(luò)請(qǐng)求,并且管理所有的 NSURLSessionTask
  • YBNetworkCache : 負(fù)責(zé)緩存處理
  • YBNetworkResponse : 回調(diào)響應(yīng)結(jié)果
  • YBBaseRequest : 負(fù)責(zé)離散數(shù)據(jù)配置、網(wǎng)絡(luò)響應(yīng)處理邏輯

集約/離散配置方式

為了更加靈活,并沒(méi)有采用 IOP 方式來(lái)做配置管理,而是采用繼承的方式來(lái)做,為了提高靈活性,定制幾率大的配置使用屬性實(shí)現(xiàn),需要重載的方法使用分類提出來(lái)看起來(lái)保證清晰。

在開(kāi)發(fā)中,需要針對(duì)不同的接口團(tuán)隊(duì)創(chuàng)建不同的YBBaseRequest子類集約配置,比如DefaultServerRequest : YBBaseRequest。在使用時(shí),可以直接實(shí)例化DefaultServerRequest或者子類化DefaultServerRequest進(jìn)行離散配置。

主要思路和 YTKNetwork 基本一樣,當(dāng)然像 CTNetworking 這樣強(qiáng)制子類化來(lái)使用接口更好管理,但是有些時(shí)候顯得有些繁瑣。

筆者這種處理方式雖然需要子類化一些YBBaseRequest進(jìn)行公共配置,但是也保證了每一個(gè)請(qǐng)求接口實(shí)例都可以任意的定制集約管理部分,防止接口抽風(fēng)。

重定向

網(wǎng)絡(luò)落地重定向重寫(xiě)此方法:

- (void)yb_redirection:(void (^)(YBRequestRedirection))redirection response:(YBNetworkResponse *)response {
    // 同步或異步的做一些事情
    redirection(YBRequestRedirectionSuccess);
}

使用redirection閉包來(lái)達(dá)到可異步重定向的能力,在這之間可以做一些具體網(wǎng)絡(luò)接口無(wú)感知的邏輯。比如攔截到所有接口都可能返回的一個(gè)錯(cuò)誤狀態(tài)碼-1,需要向服務(wù)器驗(yàn)證身份,這里就可以直接讓當(dāng)前請(qǐng)求停止redirection(YBRequestRedirectionStop),然后發(fā)起異步驗(yàn)證的網(wǎng)絡(luò)請(qǐng)求,驗(yàn)證成功后再重定向回調(diào)給業(yè)務(wù)redirection(YBRequestRedirectionSuccess),或重新發(fā)起網(wǎng)絡(luò)請(qǐng)求[self start]。

緩存處理

緩存處理專門提取一個(gè)類來(lái)包裝邏輯,而調(diào)用邏輯仍然放在YBBaseRequest,實(shí)際上代碼量很少,也好修改。

出于業(yè)務(wù)考慮,緩存支持的功能有:

  • 內(nèi)存/磁盤(pán)存儲(chǔ)方式
  • 緩存命中后是否繼續(xù)發(fā)起網(wǎng)絡(luò)請(qǐng)求
  • 緩存的有效時(shí)長(zhǎng)
  • 定制緩存的 key

對(duì)于緩存命中的回調(diào),筆者設(shè)置了專門的回調(diào)出口:

//Block
- (void)startWithCache:(nullable YBRequestCacheBlock)cache
success:(nullable YBRequestSuccessBlock)success
failure:(nullable YBRequestFailureBlock)failure;

//Delegate
- (void)request:(__kindof YBBaseRequest *)request cacheWithResponse:(YBNetworkResponse *)response;

對(duì)于 Block 方式 來(lái)說(shuō),獨(dú)立的緩存回調(diào)閉包更好管理。
對(duì)于兩種回調(diào)來(lái)說(shuō),設(shè)計(jì)一個(gè)專門的緩存回調(diào)能降低業(yè)務(wù)工程師的出錯(cuò)率。
對(duì)于網(wǎng)絡(luò)及時(shí)數(shù)據(jù)和緩存數(shù)據(jù)往往在業(yè)務(wù)處理上有細(xì)微的差別,分開(kāi)回調(diào)能避免出于疏忽而去寫(xiě)判斷if (isCache) {...} else {...}(特別是當(dāng)寫(xiě)業(yè)務(wù)的工程師并不知道這個(gè) API 緩存策略是怎樣的)。

緩存有效性驗(yàn)證

內(nèi)部會(huì)在業(yè)務(wù)處理完成網(wǎng)絡(luò)響應(yīng)數(shù)據(jù)后嘗試進(jìn)行緩存,避免將異常數(shù)據(jù)寫(xiě)入緩存(比如數(shù)據(jù)導(dǎo)致 Crash 時(shí))。且提供一個(gè)shouldCacheBlock可根據(jù)請(qǐng)求響應(yīng)成功數(shù)據(jù)判斷是否需要緩存(比如僅當(dāng) code == 0 時(shí)數(shù)據(jù)有效允許緩存)。

重復(fù)網(wǎng)絡(luò)請(qǐng)求處理

提供三種方式:

  1. 允許重復(fù)網(wǎng)絡(luò)請(qǐng)求
  2. 取消最舊的網(wǎng)絡(luò)請(qǐng)求
  3. 取消最新的網(wǎng)絡(luò)請(qǐng)求

舉幾個(gè)例子,當(dāng)接口數(shù)據(jù)并不會(huì)在短時(shí)間變化時(shí),重復(fù)發(fā)起網(wǎng)絡(luò)請(qǐng)求就會(huì)浪費(fèi)網(wǎng)絡(luò)資源,可以選擇方案 2 或 3;比如在搜索業(yè)務(wù)中,用戶往往頻繁的調(diào)用搜索接口,而發(fā)起一次搜索時(shí),之前的搜索請(qǐng)求一般是沒(méi)有意義了,就可以選用方案 2。

網(wǎng)絡(luò)請(qǐng)求釋放處理

提供三種方式:

  1. 網(wǎng)絡(luò)任務(wù)會(huì)持有YBBaseRequest實(shí)例,網(wǎng)絡(luò)任務(wù)完成YBBaseRequest實(shí)例才會(huì)釋放
  2. 網(wǎng)絡(luò)請(qǐng)求將隨著YBBaseRequest實(shí)例的釋放而取消
  3. 網(wǎng)絡(luò)請(qǐng)求和YBBaseRequest實(shí)例無(wú)關(guān)聯(lián)

實(shí)現(xiàn)網(wǎng)絡(luò)任務(wù)對(duì) YBBaseRequest 弱持有 ,當(dāng)YBNetworkManager發(fā)起請(qǐng)求時(shí),讓回調(diào)閉包捕獲弱引用的weakSelf的就行了。

而要讓YBBaseRequest釋放時(shí)自動(dòng)取消網(wǎng)絡(luò)請(qǐng)求只需要簡(jiǎn)單調(diào)用(不過(guò)在“網(wǎng)絡(luò)請(qǐng)求和 YBBaseRequest 實(shí)例無(wú)關(guān)聯(lián)”模式時(shí)是不能取消的)。

舉幾個(gè)例子,若你的控制器出棧以后希望取消未落地的網(wǎng)絡(luò)請(qǐng)求,那么就使用方案 2,注意管理好 YBBaseRequest 的生命周期就行了;若你的網(wǎng)絡(luò)請(qǐng)求是不論如何都不希望它取消的,那么使用方案 3;若你希望網(wǎng)絡(luò)請(qǐng)求任務(wù)始終持有 YBBaseRequest 實(shí)例避免它提前釋放,那么使用方案 1。

回調(diào)處理

為了讓重復(fù)網(wǎng)絡(luò)請(qǐng)求時(shí),每次回調(diào)的數(shù)據(jù)不相互影響,筆者思來(lái)想去還是額外定義了一個(gè)類,而不是直接讓YBBaseRequest持有。

至于為什么要單獨(dú)定義一個(gè)類,其一是單獨(dú)定義一個(gè)類便于拓展回調(diào)內(nèi)容,并且也降低了框架內(nèi)部數(shù)據(jù)流通過(guò)程中的成本(傳遞一個(gè)對(duì)象總比傳遞一堆對(duì)象好處理吧);其二在于一個(gè) API 請(qǐng)求實(shí)例可能發(fā)起多次網(wǎng)絡(luò)請(qǐng)求,從而可能就有多次網(wǎng)絡(luò)落地,響應(yīng)數(shù)據(jù)由YBBaseRequest持有會(huì)出現(xiàn)響應(yīng)數(shù)據(jù)被覆蓋情況。

后語(yǔ)

大體思路就是如此,至于線程安全啥的細(xì)節(jié)就不多說(shuō)了,主要是在加鎖的時(shí)候注意避免同一線程重復(fù)獲取鎖導(dǎo)致死鎖就行了。

一個(gè)看似簡(jiǎn)單的二次封裝也能有這么多值得思考的地方,精益求精并不是一件容易的事。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 江湖出來(lái)行走,遲早是要還的;網(wǎng)上發(fā)帖寫(xiě)文,一定會(huì)被噴的。 只要你說(shuō)話,只要你寫(xiě)觀點(diǎn)文,就有人噴你。 雖然很多人說(shuō),...
    開(kāi)心墻頭草閱讀 854評(píng)論 7 16
  • 1、抗拒學(xué)英文 在國(guó)內(nèi)幾乎所有的變成語(yǔ)言都是外國(guó)的,所以學(xué)技術(shù)必定要學(xué)會(huì)看英文文檔,如果不學(xué)英文,是絕對(duì)無(wú)法從菜鳥(niǎo)...
    悠悠君子閱讀 142評(píng)論 0 0
  • 2017年10月18日,在這個(gè)月里,我將連續(xù)就職了三年的工作辭掉了,這是我在心里排練了很久的狀態(tài),一直夢(mèng)想著有朝一...
    凸涂土兔閱讀 509評(píng)論 0 1
  • 其中,有一個(gè)環(huán)節(jié),是美國(guó)NLP教練學(xué)院院長(zhǎng)Helen,現(xiàn)場(chǎng)進(jìn)行教練演示。一位女士,自告奮勇,自愿作為案例,接受督導(dǎo)...
    作家明至閱讀 594評(píng)論 0 4
  • 首先是對(duì)事件回掉的定義簡(jiǎn)單的介紹, 以及在iOS平臺(tái)下事件回調(diào)的幾種方式. 名詞定義 事件 由用戶操作、具有邏輯的...
    Magic_Unique閱讀 1,978評(píng)論 0 3

友情鏈接更多精彩內(nèi)容