幾乎每一個講究的iOS專案都會有一個「網路模組」,大部分的網路請求都是通過HTTP完成,使用成熟的第三方庫諸如AFNetworking很容易搭建一個功能簡易的網路模組。但這一模組要優化好卻沒那麼簡單,是個曠日持久的工作,筆者根據自己多年的“填坑”經驗,總結一下深度優化iOS專案網路模組的方方面面,也給自己做下知識梳理。
預熱
網路模組的介面設計不在本文討論之列,設計思路有些偏個人口味,我只探討一些可以深度優化的硬性指標。在開始討論優化之前,需要讀者對網路方面的理論基礎知識有一定的掌握。讀過TCP/IP協議詳解,RFC文件就更佳。優化的東西對知識的深度和廣度都有相當的要求,各專案場景不一樣,依葫蘆畫瓢很可能會導致更多的問題,“優化”是個危險活。
關於HTTP推薦大家先閱讀下我之前的一篇總結性文章,涉及到的知識細節比較多,一一疏通之後再繼續下面的閱讀。
在開始優化之前,我們先建立樣例程式碼,這樣討論起來才能有的放矢。假設我們定義這樣兩個請求類:
1 2 3 4 5 6 7 8 9 10 |
@interface PPRequestManager : NSObject @end @implementation PPRequestManager @end @interface PPRequest : NSObject @end @implementation PPRequest @end |
一個Manager負責管理Request,一個Request基類處理公共邏輯。
優化清單
DNS對映
無論是HTTP還是Socket長連線,第一步都是DNS解析。域名根據層級「主機名.次級域名.頂級域名.根域名」去解析,每一級快取生命週期不同。在iOS裝置上幾乎每次斷網重連,重啟裝置都會使DNS快取失效,觸發重新查詢。這一步的優化對請求的延遲來說至關重要,具體優化手段可參考我之前一篇關於DNS對映的文章,配有可用的demo程式碼,這裡就不復述了。
請求壓縮
DNS查詢之後是TCP握手建立連線,併傳送請求資料。對於TCP來說,單個IP包大小受限於MSS值,大部分使用者所處網路環境下每個包的大小約在1.5KB,新建立的HTTP連線由於TCP的slow start特性,會導致本地的部分IP包本臨時快取,從而增加了整體request的延遲。所以我們應該儘可能嘗試去壓縮我們的網路請求業務資料,減少一個Request的IP包數量,或許可以讓使用者少經歷一個RTT,降低請求延遲的使用者感知。
請求合併
對於非關鍵性的業務資料,或者對實時性要求不高的請求來說,通過合併請求的方式可以減少和伺服器互動的次數,一則降低伺服器壓力,二則合併之後再壓縮能節約客戶端的流量。這類請求一般見於打點SDK,crash日誌收集等非業務型請求。
請求的安全性
請求的網路安全是個容易被忽視的話題,關於安全我之前也寫過一篇比較詳細的文章,建議細讀再配合使用HTTPS來做到基本的網路安全,這裡也不再細述了。
合理的併發數
有些業務場景會出現多個Request集中產生的情況,此時我們需要設定一個合理的併發數。併發數如果太小,會導致“劣質”的請求block住“優質”的請求。如果併發數太大,頻寬有限的場景下,會增加請求的整體延遲,請求數量對於HTTP的影響我在之前的文章中也詳細的介紹過了。
可靠性保障
可靠性保障也是個容易被忽視的方面,在深入探討之前,可以先將Request按業務屬性分類。
- 第一類:關鍵核心的業務資料,期望能100%送達伺服器。
- 第二類:重要內容請求,需要較高的請求成功率。
- 第三類:一般性內容請求,對成功率無要求。
之所以要將請求分為三類,是要在可靠性保障上做區分。理論上我們應該儘可能讓所有的請求成功率達到最高,但客戶端的流量,頻寬,手機電量,伺服器的壓力等都是有限的資源,所以我們採取的策略是隻對關鍵性的網路請求做高強度的可靠性保障。
第一類請求類似大家用微信時傳送的訊息,訊息資料一旦從輸入框發出,從使用者來的角度感知這個訊息資料是一定會到達對方的。如果網路環境差,網路模組會自動在後頭悄悄重試,一段時間後仍無法成功就通過產品互動的方式告知使用者傳送失敗了,即使失敗,請求的資料(訊息本身)一直存在客戶端。
對於這類請求的處理方式第一步不是通過網路傳送,而是持久化到Database當中。一旦入了DB,即使斷網,斷電,重啟,請求資料依然還在,只需在App重啟的時候還原請求資料,再次傳送即可。我們用程式碼來進一步闡釋。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//定義請求可靠性型別 typedef enum : NSUInteger { PPRequestReliability_PersistentToDB, PPRequestReliability_Retry, PPRequestReliability_Normal, } PPRequestReliability; //增加持久化介面 @interface PPRequest : NSObject @property (nonatomic, assign) PPRequestReliability reliability; @property (nonatomic, strong) NSNumber* rowID; @property (nonatomic, strong) NSNumber* reliabilityStatus; - (PPRequestRecord*)serializeToRecord; - (PPRequest*)deserializeFromRecord:(PPRequestRecord*)record; @end |
第一類請求是PPRequestReliability_PersistentToDB,新增了rowID用作儲存DB時的唯一標識,reliabilityStatus表示請求的當前狀態(成功 or 失敗 or 取消 or 進行中),我們再看下傳送請求的流程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@implementation PPRequestManager - (void)sendRequest:(PPRequest*)req withDelegate:(id)delegate { if (req.reliability == PPRequestReliability_PersistentToDB) { PPRequestRecord* record = [req serializeToRecord]; //save record to database } //send request } - (void)onRequestSucceed:(PPRequest*)req { //add to resend queue [_resendQueue addObject:req]; //remove from db } - (void)onRequestFail:(PPRequest*)req { //add to resend queue [_resendQueue addObject:req]; } @end |
如果判斷為第一類請求,第一步我們先將請求持久化到DB當中。
第二步傳送請求,如果請求失敗則將請求加入重試佇列,成功則從重試佇列中移除。重試佇列背後也需要一套通用機制,比如多久重試一次,重試幾次之後放棄。
遇到最惡劣的場景,請求傳送失敗之後,App被kill。我們需要在App重啟之後從DB當中重新load所有失敗的請求再次重試。
1 2 3 4 5 6 7 |
- (void)resendPreviousFailedRequest { //load from db //send requests } |
通過上述幾步基本上可以使請求的可靠性得到極大的保障。但100%是無法實現的理想,失敗的時候使用者重灌App,所有持久化的資料丟失,請求資料也就丟了,不過這種極端的場景非常少。
第二類請求的可靠性為PPRequestReliability_Retry,這類請求的例子可以是我們App啟動時使用者看到的首頁,首頁的內容從伺服器獲取,如果第一次請求就失敗體驗較差,這種場景下我們應該允許請求有機會多試幾次,增加一個retryCount即可。
1 2 3 |
//PPRequest.h @property (nonatomic, assign) int retryCount; |
一般3次的重試基本可以排除網路抖動的情況。三次失敗之後即可認為請求失敗,通過產品互動告知使用者。
第三類請求的重要性最低,比如進入Controller的UV採集打點。這類請求只需要做一次,即使失敗也不會對產品體驗產生什麼負面影響。
多通道
現在不少有技術條件的團隊都有自己的tcp長連線通道,技術再硬點的甚至配有UDP通道,UDP在丟包率高的網路環境下能極大的提高請求成功的概率。如果能同時具備HTTP,TCP,UDP三條網路通道,在某些場景下,如果不考慮流量(比如wifi),可以針對某個網路請求,兩通道或者三通道齊發,對請求成功的速度和可靠性有明顯的療效,不過客戶端和伺服器都需要針對業務場景做去重。我工作過的一個IM App在傳送訊息的時候,就是Socket配合HTTP雙通道工作。UDP在VOIP服務當中使用較多,不過據說淘寶這類大廠也部分啟用了UDP。
網路環境監控
現在網路環境雖然越來越好,Wifi,4G,3G在一二線城市都有很好的普及,但還是有不少場景會導致網路狀態突然變差,比如進電梯,做火車,人多的集會場所,從公司回家4G切Wifi等等,這些場景在生活當中並不少見,健壯的網路模組需要仔細的檢測網路的變化,針對性的做請求重試。
請求成功率監控
網路模組應該能監控當前App的網路請求成功率,對於失敗率較高的請求,帶上業務資料,手機網路環境,系統引數等等,在使用者不活躍的時候能打包上報給server端,一則能找出更多需要優化的業務場景,二則能實時監控server端的健康狀態,三則能從資料層面精確判斷每一次網路優化是否有成效。
以上是做專案當中容易遇到的優化點,有些方面只是提到,要實際操作深入優化還有很多可以細說,後續如果想到更多再補充。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!