0 引言
HTTP/2,HTTP協(xié)議的第二個主要版本,是HTTP協(xié)議自1999年HTTP1.1發(fā)布后的首個更新。于2015年2月17日被批準后,標準也于2015年5月以RFC 7540正式發(fā)表(來自維基百科)。
HTTP/2,采用了一系列優(yōu)化技術(shù)來整體提升HTTP協(xié)議的傳輸性能,如異步連接復用、頭壓縮等等,可謂是當前互聯(lián)網(wǎng)應用開發(fā)中,網(wǎng)絡層次架構(gòu)優(yōu)化的必選方案之一。Apple對于HTTP/2的態(tài)度也非常積極,5月HTTP/2正式發(fā)表后不久,便在緊接著6月召開的WWDC 2015大會中,向全球開發(fā)者宣布,iOS 9 開始支持HTTP/2。
好東西自然人人都想用。然而盡管Apple早早地宣布支持HTTP/2,但是現(xiàn)在整個技術(shù)圈內(nèi)提及的iOS網(wǎng)絡層架構(gòu)設計還大多數(shù)停留在HTTP 1.1時代,并沒有一個與時俱進的、包含HTTP/2優(yōu)化的網(wǎng)絡層架構(gòu)設計策略。對于架構(gòu)設計,我在《餓了么移動APP的架構(gòu)演進》中說過,脫離業(yè)務談架構(gòu)就是純粹的耍流氓;因此,架構(gòu)的設計一定要結(jié)合當前的業(yè)務需求來進行設計和規(guī)劃,并且做好一定的可擴展性,以應對未來的變化。
本文會結(jié)合當前的業(yè)務談談以下幾個方面內(nèi)容:
1、如何在iOS下使用HTTP/2?
2、如何設計一個iOS的網(wǎng)絡層架構(gòu)?
3、與時俱進下,我們的解決方案?
1 HTTP/2下的iOS網(wǎng)絡庫
移動端的APP,網(wǎng)絡層是一個幾乎完全不可或缺的角色。而也正是在網(wǎng)絡層,由于不同家的業(yè)務模型、業(yè)務結(jié)構(gòu)不一樣,使得網(wǎng)絡層的架構(gòu)呈現(xiàn)一種百家爭鳴的局面。另一方面,Apple對網(wǎng)絡層的API也是有比較好的封裝;即使你不是太熟悉Apple的網(wǎng)絡API,使用業(yè)界流行的AFNetworking或者ASIHttpRequest也是可以簡化不少的操作。不過后者的作者已經(jīng)多年不維護了,因此AFNetworking基本上已經(jīng)成為了iOS APP的標配。
Apple在CocoaTouch層基于CFNetworking庫提供的網(wǎng)絡API有兩個大類:NSURLConnection和NSURLSession。后者從iOS7開始出現(xiàn),并宣稱是NSURLConnection的替代者。隨著時間的推移,WWDC 2015的召開也正式宣布了iOS9中NSURLConnection的deprecated標注,完成了其歷史使命,也兌現(xiàn)了之前的承諾。不過在實際的開發(fā)過程中,我們可以發(fā)現(xiàn),盡管標注了deprecated,并不意味著NSURLConnection的庫不可以繼續(xù)使用;而且,由于習慣性問題,還是有很多的工程師仍舊執(zhí)著于NSURLConnection所帶來的熟悉味道;特別是使用AFNetworking庫的工程師們,由于AFNetworking對于NSURLConnection的封裝非常精美,并且可以根據(jù)業(yè)務需要自定義添加相應的依賴關(guān)系,使得其在實際應用中讓人感到無比的“舒服”。
然而,魚與熊掌總是不可兼得。WWDC 2015 Session711 告訴我們,從 iOS 9 才開始支持的 HTTP / 2 協(xié)議只能在 NSURLSession 中使用。這也就意味著,要想進化到 HTTP / 2 就不得不舍棄陪伴我們多年的 NSURLConnection ,而且還要將設計網(wǎng)絡層架構(gòu)的思維方式調(diào)整到 NSURLSession 上來。不過慶幸的是,AFNetworking從2.0開始也提供了NSURLSession的實現(xiàn)版本,并且相關(guān)的 API 并沒有太大的變動,對于一般性的遷移還是能夠輕松應對;并且,從AFNetworking 3.0開始,正式拋棄NSURLConnection,全面投入NSURLSession的懷抱。
如果在您的網(wǎng)絡層設計中,采用了AFNetworking來降低設計的復雜性,那么正如前面提到的,由于兩者在 API 方面并沒有太大的差異,因此在一般的網(wǎng)絡層遷移過程中可以平滑地過渡。而如果你在網(wǎng)絡層設計中直接采用了原生的 API,也不需要擔心,因為 NSURLSession 的 API 被設計的更加美妙,也更加易用。不過,盡管NSURLSession有非常多值得稱贊的地方,但是它畢竟是一種新的設計思想和理念。因此在實際的使用過程中我們會發(fā)現(xiàn)很多與之前設計思維相抵觸的地方,尤其是在網(wǎng)絡依賴性處理上,就連AFNetworking也做得不是太好,這點我們在后面的章節(jié)中還會再次提到。
綜合以上而言,技術(shù)的腳步永遠向前,僅憑 HTTP / 2 這一點,我們相信 Apple 也一定會把重心向 NSURLSession 偏移,繼續(xù)優(yōu)化她,而我們也要跟上歷史的車輪,是時候讓遲暮的 NSURLConnection 休息了。
2 iOS網(wǎng)絡層的架構(gòu)設計
架構(gòu)的設計總是和業(yè)務的發(fā)展相結(jié)合和適應的。在餓了么移動多款App的發(fā)展過程中,由于不同業(yè)務的差異性導致接口、協(xié)議等都有不同的需求,給多款App設計出一個擁有干凈API和高度內(nèi)耦合的網(wǎng)絡層成為了一項挑戰(zhàn),而這個設計也將直接影響我們APP業(yè)務工程師們的開發(fā)效率。這節(jié)主要闡述理論,拋出一些問題。在下一節(jié)會給出結(jié)合餓了么多款APP的業(yè)務下所設計的網(wǎng)絡層的解決方案。
本節(jié)我們主要討論兩點:
- 與業(yè)務相結(jié)合的網(wǎng)絡層設計
- 與安全相關(guān)的網(wǎng)絡層設計
與業(yè)務相結(jié)合的網(wǎng)絡層設計
先來看與業(yè)務相結(jié)合的網(wǎng)絡層設計。
與業(yè)務相連接最緊密的地方必然是輸入與輸出,而網(wǎng)絡層的功能無疑是接受輸入的數(shù)據(jù),挑選一個相應的通道組裝數(shù)據(jù)發(fā)送給服務器,然后將服務器返回的數(shù)據(jù)返回給上一層,即輸出數(shù)據(jù)。接下來我們從數(shù)據(jù)的角度來看看在網(wǎng)絡層設計中需要考慮些什么樣的問題。這里我們會從以下三個方面來進行闡述:
- 數(shù)據(jù)輸入
- 數(shù)據(jù)回調(diào)
- 數(shù)據(jù)轉(zhuǎn)換
數(shù)據(jù)輸入
首先是輸入過程。業(yè)務數(shù)據(jù)調(diào)用網(wǎng)絡層接口時可以稱之為輸入,這里一般會有兩種形式的設計。
第一種比較常見,很多時候會被稱為集中式的API處理,即將一些經(jīng)常使用的網(wǎng)絡層調(diào)用的代碼封裝成一到兩個函數(shù)供上層調(diào)用。上層輸入相關(guān)的參數(shù)便能取得相關(guān)的回調(diào)。如以下函數(shù):
+ (void)networkTransferWithURLString:(NSString *)urlString
andParameters:(NSDictionary *)parameters
isPOST:(BOOL)isPost
transferType:(NETWORK_TRANSFER_TYPE)transferType
andSuccessHandler:(void (^)(id responseObject))successHandler
andFailureHandler:(void (^)(NSError *error))failureHandler {
// 封裝AFN
}
另一種形式的設計,則采用一種繼承形式設計每一個API,而每一個API都對應一個類,這個類中將該API的所有參數(shù)都設定好,并提供“開始”接口和“返回”的Block,很多時候我們稱這種為分布式的API處理。一個比較通用的BaseAPI可以有下列可配置項:
typedef NS_ENUM(NSUInteger, DRDRequestMethodType) {
DRDRequestMethodTypeGET = 0,
DRDRequestMethodTypePOST = 1,
DRDRequestMethodTypeHEAD = 2,
DRDRequestMethodTypePUT = 3,
DRDRequestMethodTypePATCH = 4,
DRDRequestMethodTypeDELETE = 5
};
@interface DRDBaseAPI : NSObject
@property (nonatomic, copy, nullable) NSString *baseUrl;
@property (nonatomic, copy, nullable) void (^apiCompletionHandler)(_Nonnull id responseObject, NSError * _Nullable error);
- (DRDRequestMethodType)apiRequestMethodType;
- (DRDRequestSerializerType)apiRequestSerializerType;
- (DRDResponseSerializerType)apiResponseSerializerType;
- (void)start;
- (void)cancel;
...
@end
每一個具體的API都可以繼承自這個BaseAPI,當上層業(yè)務需要進行網(wǎng)絡調(diào)用時,實例化一個需要調(diào)用的API接口,對返回的Block進行編碼,同時開啟接口。如以下代碼:
DRDAPIPostCall *apiPost = [[DRDAPIPostCall alloc] init];
[apiPost setApiCompletionHandler:^(id responseObject, NSError * error) {
}];
[apiPost start];
這兩種網(wǎng)絡層接口的設計其實都是對應不同的業(yè)務所產(chǎn)生出來的思維,因此必然都有其優(yōu)缺點。例如,第一種形式的接口其優(yōu)點在于簡單粗暴,適用于業(yè)務邏輯相對簡單并且統(tǒng)一的RESTFUL API網(wǎng)絡接口。但是缺點也非常明顯,一旦遇上稍微復雜一些的網(wǎng)絡接口情況,便需要在ViewController里寫入大量的邏輯來達到目的。這也同時會使得原本就臃腫不堪的ViewController變得更加的龐大。
第二種的設計則優(yōu)雅得多。將大量的配置邏輯都放在了另一個類文件中進行設計,ViewController中的代碼會變得更輕盈一些。而將配置放在類中還有另一個好處,那便是可以增加許多平時不使用的可配置項,來增加整個網(wǎng)絡層的可擴展性;與此同時,每一個API對應了一個不同的類的設計,又可以讓不同的API可以有不同的表象,如分別遵循不同的JSON-RPC版本。不過這種形式的設計也存在觸目驚心的問題,那便是類爆炸。如果是小型的APP,則問題并不是那么明顯;而如果是中大型APP的話,動則上百個API,會使得后期的維護變得有些吃力。
數(shù)據(jù)回調(diào)
說完了輸入問題,接下來輸出的設計。在輸出部分的設計中,可以說是八仙過海各顯神通。
網(wǎng)絡層的傳輸大多以異步加載為主,即服務器響應后由網(wǎng)絡層來負責將數(shù)據(jù)推給上層業(yè)務線程。在iOS的體系中,也提供了很多種方式用于這種場景的處理,例如直接廣播的Notification、函數(shù)回調(diào)的delegate以及最具特色的Block,都能夠完成這種任務。那么采用哪種方式呢?在回答這個問題前我們先來看看這幾種方式其各自的優(yōu)缺點。
Notification
Notification,顧名思義的廣播,其特點在于一對多地發(fā)送相關(guān)數(shù)據(jù)的通知。優(yōu)點非常明顯,易于實現(xiàn);但缺點也很明顯,會破壞整個APP架構(gòu)設計中的層次結(jié)構(gòu),造成跨層的調(diào)用和處理。
Delegate
Delegate,最常用的的回調(diào)方式。優(yōu)點是后期易于維護且不會造成跨層的調(diào)用;缺點則是回調(diào)代碼與輸入的邏輯代碼大部分時候不會放在一起,增加了一些后期閱讀上的成本。
Block
Block是OC語言中的特性,其優(yōu)點恰好是Delegate的缺點,即它讓回調(diào)的代碼能夠和調(diào)用的代碼保持在相同位置,利于靜態(tài)代碼追蹤和邏輯思維的延續(xù)。缺點則在于容易造成循環(huán)引用(Retain Cycle);并且對于大型APP來說,埋點這種AOP行為通常在Block中難以為繼,且會造成Debug上的一些困難。
在Block的使用過程中,一定要注意使用weakSelf和strongSelf來打破循環(huán)引用。否則造成的內(nèi)存泄漏會造成后期排查的困難。
小結(jié)
也許讀者看到這會更困惑了,究竟什么樣的方案更佳?個人認為還是要從業(yè)務需求出發(fā)來進行設計,從我自身而言我更喜歡Block+Notification的形式,然后在適當?shù)臅r候輔以Delegate完成。
數(shù)據(jù)轉(zhuǎn)換
數(shù)據(jù)回調(diào)的問題已經(jīng)基本解決,但是新的問題也擺在了我們的面前:上層該看到怎樣的數(shù)據(jù)?
在這里我們會發(fā)現(xiàn)非常多的應用場景,比如大多數(shù)情況下,業(yè)務層都希望返回的是與其自身相關(guān)的數(shù)據(jù)結(jié)構(gòu)(Model實例),在這樣的前提下能夠非常地方便地對本地的數(shù)據(jù)進行相關(guān)的操作;而又比如說查詢一個操作結(jié)果的是與非,那么本身數(shù)據(jù)就只有一個yes或者no,這時候采用一個數(shù)據(jù)結(jié)構(gòu)來囊括便會顯得復雜和臃腫;又或是網(wǎng)絡層采取了JSON-RPC這樣的協(xié)議,返回回來的信息存在大量的冗余數(shù)據(jù),但上層業(yè)務卻是若水萬千只取一瓢飲。
從以上各種場景中我們可以看到,業(yè)務所需的數(shù)據(jù)形式非常多變,因此最好的方式還是交給上層自己去處理。一種常見的方法就是設定一個Delegate或者Block進行返回數(shù)據(jù)的轉(zhuǎn)換,將JSON或者XML等格式轉(zhuǎn)成所需要的數(shù)據(jù)格式以方便上層業(yè)務繼續(xù)處理。不過我個人更傾向于在API本身就實現(xiàn)好這個Delegate或者Block所描述的轉(zhuǎn)換函數(shù),這樣會讓API的層次更加清晰。下一節(jié)我會談到我們的處理方式。
與安全相關(guān)的網(wǎng)絡層設計
接下來我們來看看與安全性相關(guān)的設計。其實總體來說,使用了HTTPS基本上就已經(jīng)足夠保證你的網(wǎng)絡安全性了,這里我也就不一一列舉其好處。事實上國外多數(shù)的大公司以及國內(nèi)的BAT幾乎都已經(jīng)是全站HTTPS了,免費的SSL證書的申請難度也在不斷降低,門檻上已經(jīng)可以說是沒有門檻。因此為了站點的安全性,上HTTPS吧。
不過,HTTPS如果使用不當仍然會存在一些的小缺陷,MITMA(Man-in-the-middle attack)攻擊便是其中的一種。嘗試這樣一種情況,使用Charles這樣的抓包工具來抓取HTTPS的包,Charles會讓我們?nèi)グ惭b它自己頒發(fā)的根證書。一旦我們選擇和信任了這個根證書,我們會發(fā)現(xiàn)Charles能夠順利地顯示整個HTTPS通信的情況了。對于這種中間人攻擊,目前一般的解決方案即采取SSL Pinning,即將服務器的公鑰證書與整個APP打包在一起發(fā)出,然后在網(wǎng)絡請求時候?qū)⒎掌靼l(fā)送過來的證書與本地證書進行比較,從而避免中間人攻擊的可能性。關(guān)于這部分的設計,AFNetworking已經(jīng)有相關(guān)的實現(xiàn)了,我在《正確使用AFNetworking的SSL保證網(wǎng)絡安全》有過詳細闡述,這里就不再贅述了。
3 與時俱進下的解決方案
說完了理論,現(xiàn)在結(jié)合實際來談談我們的解決方案。
我們要使用HTTP/2,那么在網(wǎng)絡庫的選擇上必然需要使用NSURLSession來達到目的,并且我們也不希望自己去實現(xiàn)序列化以及RESTFUL的復雜性,因此AFNetworking3.0成了一個比較不錯的選擇。但是似乎僅僅有這些還不夠。接下來會分為以下幾個部分來談談我們的解決方案:
- 業(yè)務協(xié)議
- 輸入與配置
- 數(shù)據(jù)轉(zhuǎn)換與輸出
- 安全
業(yè)務協(xié)議
從業(yè)務協(xié)議上來說,餓了么眾多APP中,每款APP都有其自身的特點,例如有些采取RESTFUL的設計,也有采用JSON-RPC的設計來達到業(yè)務目的。這時候如果采取集中式的API設計,相對應JSON-RPC會產(chǎn)生大量的RPC協(xié)議封裝代碼。并且對于不同版本、類型的RPC協(xié)議,需要有不同的集中函數(shù)或者增加大量的參數(shù)來處理其中的差異性。如果采取分布式的API設計,則可以將這部分協(xié)議代碼放進API自身類中來進行處理。在這里,我設計了一個RPCProtocol,由業(yè)務方自己來定義所需要遵循的業(yè)務RPC標準。而每個API都保存一個rpcDelegate字段來自定義自己的上層協(xié)議,而如果為空時,即代表著不進行RPC封裝而是直接發(fā)送,從而達到JSON-RPC和RESTFUL在一個APP共存的目的;并且由于每個API都可以指定不同的rpcDelegate,因此可以適用于服務器端不同的RPC版本兼容性。這里,RPCProtocol會有一些這樣的闡述:
NS_ASSUME_NONNULL_BEGIN
@protocol DRDRPCProtocol <NSObject>
- (nullable NSString *)rpcRequestUrlWithAPI:(DRDBaseAPI *)api;
- (nullable id)rpcRequestParamsWithAPI:(DRDBaseAPI *)api;
- (nullable id)rpcResponseObjReformer:(id)responseObject withAPI:(DRDBaseAPI *)api;
- (nullable id)rpcResultWithFormattedResponse:(id)formattedResponseObj withAPI:(DRDBaseAPI *)api;
- (NSError *)rpcErrorWithFormattedResponse:(id)formattedResponseObj withAPI:(DRDBaseAPI *)api;
@end
NS_ASSUME_NONNULL_END
Protocol中會對RequestURL,RequestParams進行RPC裝箱設計,并且對于回包,也有rpcResponseObjReformer進行拆箱,將可用值和錯誤值交給rpcResultWithFormattedResponse以及rpcErrorWithFormattedResponse處理后,再返回給業(yè)務上層。
通過使用RPCProtocol,我們保持了整個網(wǎng)絡層上層協(xié)議的一種可擴展性。
輸入與配置
解決完協(xié)議的問題,我們再來看看輸入和配置的問題。
簡單一看,似乎輸入和配置的關(guān)系并不大;的確如此,在集中式的API設計時,更多的時候是傳參,那么現(xiàn)在采取分散式API設計時,由于每個API先繼承BaseAPI,然后再在子類中去覆蓋每個需要配置的函數(shù),因此看起來每個API都更像是一個配置的過程。
配置完成后的每個API,過去的方式可能是每個API都對應一個APIManager,反饋到AFNetworking上呢,可能就是每個API都使用一個AFHTTPRequestOperationManager,然后在這個Manager去發(fā)起請求。
不過,這種形式在HTTP/2上會顯得愚笨。我們都知道HTTP/2是復用TCP管道連接的,這點體現(xiàn)在NSURLSession底層對于每個session是對多個task進行連接的復用。如果繼續(xù)采取過去的方式多個AFHTTPSession來請求,會導致多個TCP連接,并且連接數(shù)不可控。而復用Session的話,可以充分利用NSURLSession的并發(fā)控制以及HTTP/2的高復用來提高性能。這點我在我的另一片文章《別說你會AFNetworking3.0/NSURLSession》有過詳細闡述,這里也不再贅述了。
因此,我這里將每個配置好的API都扔到一個共享的APIManager中,即分散式API回歸到集中式調(diào)用的懷抱。由APIManager來負責提供SessionManager的策略。并且通過Global的配置,來決定每個Session的最大并發(fā)數(shù)。同時將AFNetworking封裝進整個APIManager,保持對外透明。這樣如果未來如果升級AFNetworking版本,或者打算切換到直接使用NSURLSession來處理網(wǎng)絡連接,上層業(yè)務API也不需要有任何的改動,進一步增強了未來的可配置性。
上節(jié)我們提到了分散式API,它具有一個最大的缺點,那便是導致類的爆炸,產(chǎn)生出成白上千的API的文件。在這一點上,我設計了另一個BaseAPI,我稱之為GeneralAPI。這個API與之前的有什么不同呢?先來看一個典型的GET請求API的類文件內(nèi)容:
- (NSString *)requestMethod {
return @"get";
}
- (id)requestParameters {
return nil;
}
- (DRDRequestMethodType)apiRequestMethodType {
return DRDRequestMethodTypeGET;
}
- (DRDRequestSerializerType)apiRequestSerializerType {
return DRDRequestSerializerTypeHTTP;
}
- (DRDResponseSerializerType)apiResponseSerializerType {
return DRDResponseSerializerTypeHTTP;
}
在這個API里,覆蓋好幾個函數(shù)即可以完成相應的內(nèi)容。而在ViewController中進行調(diào)用會有這樣的代碼。
DRDAPIGetCall *apiGet = [[DRDAPIGetCall alloc] init];
[apiGet setApiCompletionHandler:^(id responseObject, NSError * error) {
NSLog(@"responseObject is %@", responseObject);
if (error) {
NSLog(@"Error is %@", error.localizedDescription);
}
}];
[apiGet start];
而如果使用GeneralAPI,則在ViewController中,會是這樣的代碼。
DRDGeneralAPI *apiGeGet = [[DRDGeneralAPI alloc] initWithRequestMethod:@"get"];
apiGeGet.apiRequestMethodType = DRDRequestMethodTypeGET;
apiGeGet.apiRequestSerializerType = DRDRequestSerializerTypeHTTP;
apiGeGet.apiResponseSerializerType = DRDResponseSerializerTypeHTTP;
[apiGeGet setApiCompletionHandler:^(id responseObject, NSError * error) {
NSLog(@"responseObject is %@", responseObject);
if (error) {
NSLog(@"Error is %@", error.localizedDescription);
}
}];
[apiGeGet start];
沒錯,使用GeneralAPI,將一些簡單的,不復雜的API的配置,直接使用property來直接在ViewController中賦值,這樣就降低了一些簡單的API生成類文件導致的爆炸。增強了整個網(wǎng)絡層的易用性和簡便性。
數(shù)據(jù)轉(zhuǎn)換與輸出
再來看看數(shù)據(jù)轉(zhuǎn)換與輸出。
這里我們將數(shù)據(jù)轉(zhuǎn)換和輸出放在一起來討論。上節(jié)提到的交付什么樣的數(shù)據(jù)給業(yè)務層、數(shù)據(jù)轉(zhuǎn)換的操作,但并沒有給出答案?,F(xiàn)在我們來結(jié)合業(yè)務來看看怎樣來操作數(shù)據(jù)的轉(zhuǎn)換。
前面提到過,餓了么各個APP產(chǎn)品線都會有自己的性格和特點,數(shù)據(jù)轉(zhuǎn)換上也會呈現(xiàn)各自喜好的局面。有人喜歡用Mantle,有人用過MJExtension,也有采取YYModel,也有大牛自己實現(xiàn)JSON<-->Model的轉(zhuǎn)換。因此,網(wǎng)絡層最好并不過問數(shù)據(jù)的轉(zhuǎn)換方式以及過程,而是提供一個機會給上層業(yè)務來讓其自己采用自己的方式進行轉(zhuǎn)換。因此,我在API的設計中,提供了這樣一個函數(shù):
- (nullable id)apiResponseObjReformer:(id)responseObject andError:(NSError * _Nullable)error;
GeneralAPI中對應為:
@property (nonatomic, copy, nullable) id _Nullable (^apiResponseObjReformerBlock)(id responseObject, NSError * _Nullable error);
這個函數(shù)默認為空,參數(shù)中的responseObject為rpcDelegate拆包后產(chǎn)生的resposneObject。在這個函數(shù)中,上層業(yè)務可以將responseObject進行Model的轉(zhuǎn)換工作,將Model作為返回值交給apiCompletionHandler函數(shù)進行操作。這樣既保持了ViewController中的簡潔性,也在保證了各個上層業(yè)務對于JSON<-->Model轉(zhuǎn)換的多樣性的同時,保證了未來轉(zhuǎn)換方式的可擴展性。
安全
最后來談談安全。
其實安全到這一塊可談的已經(jīng)不多了,該談的都在上節(jié)中談完了。由于APIManager中采用了AFNetworking簡化SSL Pinning的復雜度,因此在網(wǎng)絡層中只需要三步便可以完成SSL Pinning。
- 實例化一個
DRDSecurityPolicy, 將SecurityPolicy中的DRDSSLPinningMode設置成為DRDSSLPinningModePublicKey或者DRDSSLPinningModeCertificate。 - 將API的
apiSecurityPolicy設定為以上實例。 - 將服務器的公鑰證書放到APP Bundle中。
結(jié)束!
One More Thing!
**多網(wǎng)絡請求的并發(fā)執(zhí)行。 **
設想這樣一個場景,我們希望有若干個網(wǎng)絡請求,當這些請求都結(jié)束后才通知上層應用工作的完成。
早期采取AFNetworking的AFHTTPRequestOperationManager方案時,AFN對整個系統(tǒng)都采用了NSOperation以及NSOperationQueue來控制網(wǎng)絡請求的依賴性。但是HTTP/2后的NSURLSession由于無法使用這種設計,因此造成這種并發(fā)依賴難以為繼。
值得慶幸的是,AFNetworking在SessionManager的實現(xiàn)中依舊保留有dispatch_group_t的接口。因此我們使用dispatch_group創(chuàng)建了DRDAPIBatchAPIRequests類,來達到并發(fā)網(wǎng)絡請求的目的。
@interface DRDAPIBatchAPIRequests : NSObject
@property (nonatomic, strong, readonly, nullable) NSMutableSet *apiRequestsSet;
@property (nonatomic, weak, nullable) id<DRDAPIBatchAPIRequestsProtocol> delegate;
- (void)addAPIRequest:(nonnull DRDBaseAPI *)api;
- (void)addBatchAPIRequests:(nonnull NSSet *)apis;
- (void)start;
@end
一如既往,保持API層面簡潔。這里我們使用了Delegate來處理多個網(wǎng)絡請求完成后的回調(diào)操作:
@protocol DRDAPIBatchAPIRequestsProtocol <NSObject>
- (void)batchAPIRequestsDidFinished:(nonnull DRDAPIBatchAPIRequests *)batchApis;
@end
在這個設計中,每個API完成后,都可以有自己的回調(diào)。所有的并發(fā)網(wǎng)絡請求完成后仍舊可以有一個公用的回調(diào),讓整體設計保持一個離散+集中都能得到很好處理的情形,保證上層的業(yè)務可擴展性。
結(jié)語
結(jié)合業(yè)務,才能談及架構(gòu);持續(xù)調(diào)優(yōu),才能不斷地與時俱進。
最后我們也開源了我們的DRDNetworking庫,歡迎大家多提寶貴意見。
本文謝絕轉(zhuǎn)載,如需轉(zhuǎn)載請簡信于我,謝謝。