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