網絡是任何一個系統/平臺的基礎功能,在iOS上也同樣是這樣的.從NSURLConnection
到NSURLSession
,從ASIHttpRequest
到目前最主流的AFNetworking
.
事實上,一個項目中通常會有一個更高層次的封裝.可以是自行基于NSURLConnection
進行封裝,也可以基于AFNetworking
或者Alamofire
.
這里提供一個參考的姿勢:ZCNetworking
結構
主要提供了3層
- 網絡常用操作的抽象層:ZCNetworking,這里是基于
AFNetworking
- 針對項目的具體操作Runner層:ZCApiRunner;提供大量的功能,配置以簡化項目中的實際使用.
- Actions:普通/上傳/下載操作的封裝;包括參數/url等,也提供了一些便利操作,比如好用的log.
抽象層
這是比較重要的一層,將網絡操作和具體實現隔離開來.僅僅暴露出一些task.
有了這一層,那么替換基礎庫就成為可能.ZCNetworking是基于AFNetworking,或許有一天會修改成其他的networking或者是直接使用NSURLSession
呢?如果改動,只需要改動這一層即可,而整個項目不會受到任何影響.
這一層本身只提供幾個基礎方法:
- 數據獲取: sendRequest
- 上傳: uploadTask
- 下載:downloadFile;還包含了一個下載圖片的方法
- 一些基礎的操作方法,比如cookie等
基礎方法中也提供了2種形式,以方便配置:包含NSURLSessionConfiguration
和不包含.當然不包含則是默認.
暴露的接口較少,當然完全可以根據實際需求進行擴充.不過需要確定的一點就是:接口完全和任意第三方類無關.這樣才能使得替換底層實現與上層無關.
接口的實現沒有太多要注意的.按照正常的AFNetworking
使用即可.
Runner
這一層是針對于項目的具體實現,做一些公共的配置/設置.包括:
- 區分正式/測試服務器
- 公共參數的處理,例如headers,params
- 對于邏輯成功/失敗的處理
- 對于數據獲取/上傳/下載的處理
- 對于batch操作的處理
- 對于chain操作的處理
服務器區分
通常項目會有至少2個以上的服務器,正式和測試服務器.而對于服務器地址的管理,有多種方式.可以是純手工的管理;可以是參數的配置,例如做一個宏;也可以做多個target;
純手工當然不可取,太容易出錯.好吧..說的就是我..的確因為疏忽翻過這樣的錯誤.
配置本質上也是純手工,只是設立了一個總開關.但是一旦疏忽,仍然有風險.
target會不會太heavy了點?假設還有cdn呢...
于是ZCNetworking
中,根據當前環境來自行決定使用的服務器:
- (void)startWithDebugDomain:(NSString *)debug releaseDomain:(NSString *)release {
_debugDomain = debug;
_releaseDomain = release;
}
- (NSString *)currentDomain {
if (_forceDomain.length > 0) {
return _forceDomain;
}
else {
#ifdef DEBUG
return _debugDomain;
#else
return _releaseDomain;
#endif
}
}
其中還增加了一個forceDomain,可以強制使用某個環境,這樣便于調試.
公共參數
大多數項目會有這樣的需求.例如我這里會為每一個請求中加上這樣一組header:
headers[@"X-REQUESTED-WITH"] = @"1";
項目中也會需要公共參數,例如每條api需要版本號和平臺等.
ZCNetworking
在Runner中提供了相應的接口:addtionalHeaders
和globleParams
.
邏輯判定
基礎網絡只能夠判定物理上是否成功.比如是否是http 200等.但是在很多時候,邏輯上的失敗也是失敗,應該進入failure流程,不應該進入success流程再進行判定.
例如登錄操作.用戶名密碼錯誤然后返回.此時物理上成功(http 200),但是邏輯上失敗.則應當進入失敗流程.
對于一些公共錯誤,可能會有公共的操作方式.例如token/cookie過期導致登錄失效,那么會彈出登錄UI等.
所以會有幾個操作:
codeKey
/successCodes
/warningCodes
&&handler
codeKey則是返回字典的key
successCodes是個數組.如果返回字典的codekey字段的值滿足successCodes,則認為邏輯成功,否則邏輯失敗
warningCodes主要處理通用的錯誤,例如登錄失效.handler當然就是處理的方式了.
是否邏輯成功完全依賴successCodes,和warningCodes無關.
不過這需要服務端提供類似的邏輯才行,如果提供不了,不設置即可.將不會對返回的邏輯狀態進行處理,僅僅依賴物理狀態.
請求的操作
一共就數據請求,上傳,下載三大類.對應NSURLSessionDataTask
/NSURLSessionUploadTask
/NSURLSessionDownloadTask
利用之前的抽象層進行請求即可.不過請求內容已經被封裝成Action類型.包括url,params等東西.
當物理狀態返回后,根據配置對邏輯狀態進行檢查(不檢查),最終返回相應的數據.
在請求中,有log是最方便的.以前關于調試,一般就2種方式:
- 斷點
- 使用工具,例如charles.
不過斷點不太方便,涉及到變量以及作用于的問題.針對個別問題還成,針對每一個請求都調試一翻,效率較低.
charles很好用,就是有點貴...
所以如果附帶log信息的話,可能性價比較高.ZCNetworking
提供了一些log信息:
- url和參數,以xxx.com/action?a=xx&b=xx的方式拼接,對于部分get請求可以直接用瀏覽器調用.
- method/header/params
- 對于error的log,包括物理和邏輯上的
- 返回值log,方便查看數據結構
log信息由action中的參數showLog
來控制.
batch
不算特別常用的功能.但似乎也有點用.
例如在一個頁面中,需要調用多條api才能將數據獲取完畢,然后渲染界面.當然,這種方式顯然不太好,加入某條api出錯了呢?
在巧哥的YTKNetworking
中也提供了同樣的功能.使用一個count進行計數.當請求返回,則在返回中count++.當count等于請求的個數,則執行完畢.
ZCNetworking
中,通過dispatch group處理這個功能.不過該功能有2個策略.
1.batch中一旦出錯,立刻停止,返回錯誤.
2.batch中一旦出錯,繼續執行,最終返回一個字典.key為url,value為返回值.或許是object,或許是error.當然如果都成功,則返回字典.key為url,value為object.
ZCNetworking
選擇的是第一種策略.當然你也可以選擇其他的策略.
chain
也不算特別常用的功能.也似乎有點用.
例如產品是必須先登錄->在獲取數據.
YTKNetworking
類似于遞歸的感覺,通過next index計數,在請求完成后繼續執行next,直到請求隊列完畢.
ZCNetworking
中使用semaphore來處理chain,不過遇上了一個坑.
信號量是一個簡單的思路.類似于餐廳座位.有座位了就進入,沒座位了就等待.進入后,座位少一張,出來后座位多一張,下個人才能進.
然而,在創建了一個信號量以后,使用AFHTTPSessionManager
發送get請求居然沒有反應!而使用NSURLSession
卻可以請求.
查找一番后,問題出現在了兩個main thread死鎖的地方.也就是信號量和AFHTTPSessionManager
的默認complate queue.這個時候,手動設置complate queue即可:
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
返回值依然是一個字典,key為url.
當然會出現url相同的情況,這個時候key如何處理可以多斟酌一番,加上index?
more
需求的功能當然還可以有更多,例如巧哥提供了緩存,返回值驗證,斷點續傳等
有必要的話完全可以繼續擴展
Action(Model)
網絡庫的核心思路是把每一個請求封裝成對象.所以每一個請求對應一個Action
請求有3種,action當然也就有3個.
- ZCApiAction
- ZCApiUploadAction
- ZCApiDownloadAction
后2種繼承自第一種.action中主要包含api的請求相關信息,例如url,params等.也包含一些api的控制信息,例如log開關.最后提供了2個回調,實現"插件機制":
typedef void (^ZCActionComplation) (BOOL isSuccess);
typedef void (^ZCVoidBlock) (void);
@property (nonatomic, copy) ZCVoidBlock actionWillInvokeBlock;
@property (nonatomic, copy) ZCActionComplation actionDidInvokeBlock;
通過這兩個回調,可以在一個請求之前,顯示相應的hud,請求完畢后顯示成功或者失敗,然后去除.
在upload action中,需要支持單文件和多文件上傳兩種方式.所以提供了2組值(data/name/filename/mime):單個的形式(NSData和NSString)以及數組的形式.
使用
沒有提供pod~~~可以把源文件拷貝,然后import "ZCApiLauncher.h"即可.
只是希望討論一個恰當的方式,實際上每個團隊都會自己維護一套適合自己的網絡庫.合適自己項目約定的才是最好的.
才疏學淺,有不對的地方請指正.