DEMO
https://github.com/albertjson/SZCEvolution
YTKNetwork介紹
YTKNetwork 是猿題庫(kù) iOS 研發(fā)團(tuán)隊(duì)基于?AFNetworking?封裝的 iOS 網(wǎng)絡(luò)庫(kù),其實(shí)現(xiàn)了一套 High Level 的 API,提供了更高層次的網(wǎng)絡(luò)訪問(wèn)抽象。目前在 GitHub 上已有 3600+ star ,是 Network 中的新星。
YTKNetwork提供的主要功能
支持按時(shí)間緩存和版本號(hào)緩存網(wǎng)絡(luò)請(qǐng)求內(nèi)容
支持統(tǒng)一設(shè)置服務(wù)器和 CDN 的地址
支持檢查返回 JSON 內(nèi)容的合法性
支持?block?和?delegate?兩種模式的回調(diào)方式
支持批量的網(wǎng)絡(luò)請(qǐng)求發(fā)送,并統(tǒng)一設(shè)置它們的回調(diào)(實(shí)現(xiàn)在?YTKBatchRequest?類中)
支持方便地設(shè)置有相互依賴的網(wǎng)絡(luò)請(qǐng)求的發(fā)送,例如:發(fā)送請(qǐng)求 A,根據(jù)請(qǐng)求 A 的結(jié)果,選擇性的發(fā)送請(qǐng)求 B 和 C,再根據(jù) B 和 C 的結(jié)果,選擇性的發(fā)送請(qǐng)求 D。(實(shí)現(xiàn)在?YTKChainRequest?類中)
支持網(wǎng)絡(luò)請(qǐng)求 URL 的 filter,可以統(tǒng)一為網(wǎng)絡(luò)請(qǐng)求加上一些參數(shù),或者修改一些路徑。
定義了一套插件機(jī)制,可以很方便地為 YTKNetwork 增加功能。猿題庫(kù)官方現(xiàn)在提供了一個(gè)插件,可以在某些網(wǎng)絡(luò)請(qǐng)求發(fā)起時(shí),在界面上顯示“正在加載”的 HUD。
YTKNetwork 的基本思想
YTKNetwork 的基本的思想是把每一個(gè)網(wǎng)絡(luò)請(qǐng)求封裝成對(duì)象。所以使用 YTKNetwork,你的每一個(gè)請(qǐng)求都需要繼承 YTKRequest 類,通過(guò)覆蓋父類的一些方法來(lái)構(gòu)造指定的網(wǎng)絡(luò)請(qǐng)求。
把每一個(gè)網(wǎng)絡(luò)請(qǐng)求封裝成對(duì)象其實(shí)是使用了設(shè)計(jì)模式中的 Command 模式,它有以下好處:
將網(wǎng)絡(luò)請(qǐng)求與具體的第三方庫(kù)依賴隔離,方便以后更換底層的網(wǎng)絡(luò)庫(kù)。
方便在基類中處理公共邏輯,例如猿題庫(kù)的數(shù)據(jù)版本號(hào)信息就統(tǒng)一在基類中處理。
方便在基類中處理緩存邏輯,以及其它一些公共邏輯。
方便做對(duì)象的持久化。
當(dāng)然,如果說(shuō)它有什么不好,那就是如果你的工程非常簡(jiǎn)單,這么寫(xiě)會(huì)顯得沒(méi)有直接用?AFNetworking?將請(qǐng)求邏輯寫(xiě)在 Controller 中方便,所以 YTKNetwork 并不合適特別簡(jiǎn)單的項(xiàng)目。
關(guān)于集約式和離散式
集約式
介紹:即項(xiàng)目中的每個(gè)請(qǐng)求都會(huì)走統(tǒng)一的入口,對(duì)外暴露了請(qǐng)求的 URL 和 Param 以及請(qǐng)求方式,入口一般都是通過(guò)單例?來(lái)實(shí)現(xiàn),AFNetworking 的官方 demo 就是采用的集約式的方式對(duì)網(wǎng)絡(luò)請(qǐng)求進(jìn)行的封裝,也是目前比較流行的網(wǎng)絡(luò)請(qǐng)求方式。
優(yōu)點(diǎn):
使用便捷,能實(shí)現(xiàn)快速開(kāi)發(fā)
缺點(diǎn):
對(duì)每個(gè)請(qǐng)求的定制型不夠強(qiáng)
不方便后期業(yè)務(wù)拓展
離散式
介紹:即每個(gè)網(wǎng)絡(luò)請(qǐng)求類都是一個(gè)對(duì)象,它的 URL 以及請(qǐng)求方式和響應(yīng)方式 均不暴露給外部調(diào)用。只能內(nèi)部通過(guò)?重載或?qū)崿F(xiàn)協(xié)議?的方式來(lái)指定,外部調(diào)用只需要傳 Param 即可,YTKNetwork就是采用的這種網(wǎng)絡(luò)請(qǐng)求方式。
優(yōu)點(diǎn):
URL 以及請(qǐng)求和響應(yīng)方式不暴露給外部,避免外部調(diào)用的時(shí)候?qū)戝e(cuò)
業(yè)務(wù)方使用起來(lái)較簡(jiǎn)單,業(yè)務(wù)使用者不需要去關(guān)心它的內(nèi)部實(shí)現(xiàn)
可定制性強(qiáng),可以為每個(gè)請(qǐng)求指定請(qǐng)求的超時(shí)時(shí)間以及緩存的周期
缺點(diǎn):
網(wǎng)絡(luò)層需要業(yè)務(wù)實(shí)現(xiàn)方去寫(xiě),變相的增加了部分工作量
文件增多,程序包會(huì)變大[倒也不是特別大]
在微脈的iOS客戶端,由于最初人員較少,且業(yè)務(wù)變更較頻繁。故使用的就是集約式請(qǐng)求。不過(guò)考慮到為實(shí)現(xiàn)業(yè)務(wù)便捷性以及可拓展性,故增加了?RequestHeader?請(qǐng)求頭,以及?WMHttpHelper?網(wǎng)絡(luò)操作工具類?;旧弦褲M足于目前的開(kāi)發(fā)模式
不過(guò)長(zhǎng)遠(yuǎn)來(lái)看,轉(zhuǎn)成離散式的網(wǎng)絡(luò)請(qǐng)求也是有必要的。
安裝
你可以在 Podfile 中加入下面一行代碼來(lái)使用 YTKNetwork
pod 'YTKNetwork'
集成至項(xiàng)目
項(xiàng)目文件介紹

YTKNetwork源碼
YTKBaseRequest:為請(qǐng)求的基類,內(nèi)部聲明了請(qǐng)求的常用 API :
比如請(qǐng)求方式,請(qǐng)求解析方式,響應(yīng)解析方式,請(qǐng)求參數(shù)等等。它的用意是讓子類去實(shí)現(xiàn)的,本身不做實(shí)現(xiàn)。
YTKRequest:是?YTKBaseRequest?的子類,在其基礎(chǔ)上支持了緩存,并且提供了豐富的緩存策略?;旧享?xiàng)目中使用都是繼承于?YTKRequest?去寫(xiě)業(yè)務(wù)的 Request。
YTKNetworkAgent:真正做網(wǎng)絡(luò)請(qǐng)求的類,在內(nèi)部跟?AFNetworking?直接交互,調(diào)用了?AFNetworking?提供的各種請(qǐng)求,當(dāng)然,如果底層想切換其他第三方,在這個(gè)類中替換掉就行了。
YTKNetworkConfig:該文件為網(wǎng)絡(luò)請(qǐng)求的統(tǒng)一配置類,提供了設(shè)置?baseUrl?cdnUrl?等基礎(chǔ)請(qǐng)求路徑,可以給所有的請(qǐng)求增加參數(shù)等等。
YTKBatchRequest:為批量進(jìn)行網(wǎng)絡(luò)請(qǐng)求而生,提供了代理和 block 兩種方式給外部使用
YTKChainRequest:當(dāng)多個(gè)請(qǐng)求之間有關(guān)聯(lián)的時(shí)候采用此類去實(shí)現(xiàn)非常方便,即下一個(gè)請(qǐng)求可能要根據(jù)上個(gè)請(qǐng)求返回的數(shù)據(jù)進(jìn)行請(qǐng)求。
YTKBatchRequestAgent,YTKChainRequestAgent:分別是?YTKBatchRequest,YTKChainRequest?的操作類,不需要也無(wú)妨主動(dòng)調(diào)用
集成文件介紹

My Project Table
這是 Demo 工程的我新增的文件,一般情況下,不建議直接繼承于?YTKRequest?類去寫(xiě)業(yè)務(wù),需要自己寫(xiě)請(qǐng)求的基類,具體業(yè)務(wù)請(qǐng)求再繼承于改項(xiàng)目基類,避免因新版本 YTKRequest 中修改了部分實(shí)現(xiàn)的默認(rèn)值導(dǎo)致的程序需要做大量的修改。其中:ZCBaseRequest,ZCBatchRequest,ZCChainRequest?就是 demo 項(xiàng)目的基類。ZCJSONModel?是 JSON 轉(zhuǎn) Model 的基類,而?ZCHTTPError?是用于自定義錯(cuò)誤信息的

single http example
這是 Demo 工程中具體某個(gè)請(qǐng)求的實(shí)例。這種展現(xiàn)方式很清晰,ZCGetInfoParam?是請(qǐng)求的入?yún)㈩?,ZCMeGetInfoManger是具體的請(qǐng)求操作類,?ZCGetInfoModel是出參類。不過(guò)如果入?yún)⒑统鰠⒑苌?,可以只有一個(gè)manger類
相關(guān)問(wèn)題思考
我這里不想介紹?YTKNetwork?的基礎(chǔ)和高級(jí)使用教程。如果想了解基礎(chǔ)以及高級(jí)使用教程可以看這里
這篇文章重在介紹集成以及使用過(guò)程中遇到一些問(wèn)題以及解決方案
1>JSON轉(zhuǎn)Model的問(wèn)題
對(duì)于稍微復(fù)雜的項(xiàng)目,可能某些接口返回?cái)?shù)據(jù)有十多個(gè),使用的時(shí)候不可能從字典中一個(gè)一個(gè)讀取出來(lái),然后再做??空處理,一般都是采用轉(zhuǎn) Model 的方式 轉(zhuǎn)換成具體的業(yè)務(wù)模型,從業(yè)務(wù)模型中獲取具體數(shù)據(jù),常見(jiàn)的有?JSONModel,Mantle,MJExtension?等第三方庫(kù),本文以JSONModel為例,來(lái)實(shí)現(xiàn)框架內(nèi)部解析成 Model
在?YTKBaseRequest?新增 JSONModel 屬性
/// JsonModel類
@property (nonatomic, strong, readonly, nullable) id? responseJSONModel;
在?YTKBaseRequest?新增 modelClass 函數(shù),用于子類去實(shí)現(xiàn),表明要轉(zhuǎn)換的具體 model 類的類名
YTKBaseRequest.h
/// model對(duì)應(yīng)的類,子類實(shí)現(xiàn)的話會(huì)直接映射到該model類并進(jìn)行初始化操作
- (Class)modelClass;
YTKBaseRequest.m
- (Class)modelClass
{
? return nil;
}
查看源碼不難發(fā)現(xiàn),真正處理網(wǎng)絡(luò)請(qǐng)求成功和失敗的地方是?YTKNetworkAgent?類,在?- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error?和?- (void)requestDidSucceedWithRequest:(YTKBaseRequest *)request,- (void)requestDidFailWithRequest:(YTKBaseRequest *)request error:(NSError *)error?這三個(gè)方法。
具體操作為
- (void)requestDidSucceedWithRequest:(YTKBaseRequest *)request {
@autoreleasepool {
? ? [request requestCompletePreprocessor];
? ? [self JSONConvertModel:request];
}
dispatch_async(dispatch_get_main_queue(), ^{
? ? [request toggleAccessoriesWillStopCallBack];
? ? [request requestCompleteFilter];
? ? if (request.delegate != nil) {
? ? ? ? [request.delegate requestFinished:request];
? ? }
? ? if (request.successCompletionBlock) {
? ? ? ? request.successCompletionBlock(request);
? ? }
? ? [request toggleAccessoriesDidStopCallBack];
});
}
///json轉(zhuǎn)model的具體方法
- (void)JSONConvertModel:(YTKBaseRequest*)request
{
Class modelClass = [request modelClass];
if (!modelClass) {
? ? return;
}
NSError * error = nil;
if ([request.responseJSONObject isKindOfClass:[NSDictionary class]]) {
? ? request.responseJSONModel = [[modelClass alloc] initWithDictionary:request.responseJSONObject error:&error];
}else if ([request.responseJSONObject isKindOfClass:[NSArray class]]){
? ? request.responseJSONModel = [modelClass arrayOfModelsFromDictionaries:request.responseJSONObject error:&error];
}else if {
? ? //這里不做處理,因?yàn)锳FNetworking如果返回的數(shù)據(jù)為null的時(shí)候會(huì)調(diào)用失敗的回調(diào)
}
if (error) {
? ? YTKLog(@"Request JSON---JSONModel Failed =%@",error);
}
}
在?YTKRequest?類中也需要新增緩存類的model,具體代碼為
YTKRequest.m
@property (nonatomic, strong) id cacheJSONModel;///TTT
- (id)responseJSONModel {
if (_cacheJSONModel) {
? ? return _cacheJSONModel;
}
return [super responseJSONModel];
}
- (BOOL)loadCacheData {
NSString *path = [self cacheFilePath];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
if ([fileManager fileExistsAtPath:path isDirectory:nil]) {
? ? NSData *data = [NSData dataWithContentsOfFile:path];
? ? _cacheData = data;
? ? _cacheString = [[NSString alloc] initWithData:_cacheData encoding:self.cacheMetadata.stringEncoding];
? ? switch (self.responseSerializerType) {
? ? ? ? case YTKResponseSerializerTypeHTTP:
? ? ? ? ? ? // Do nothing.
? ? ? ? ? ? return YES;
? ? ? ? case YTKResponseSerializerTypeJSON:
? ? ? ? ? ? _cacheJSON = [NSJSONSerialization JSONObjectWithData:_cacheData options:(NSJSONReadingOptions)0 error:&error];
? ? ? ? ? ? if (!error) {
? ? ? ? ? ? ? ? [self JSONConvertModel:_cacheJSON];
? ? ? ? ? ? }
? ? ? ? ? ? return error == nil;
? ? ? ? case YTKResponseSerializerTypeXMLParser:
? ? ? ? ? ? _cacheXML = [[NSXMLParser alloc] initWithData:_cacheData];
? ? ? ? ? ? return YES;
? ? }
}
return NO;
}
- (void)JSONConvertModel:(YTKBaseRequest*)request
{
? ///跟第二步的實(shí)現(xiàn)方式一樣
}
到這里基本上已經(jīng)實(shí)現(xiàn)了 json-model,具體的業(yè)務(wù)代碼為:
- (void)loadCacheData {
NSString *userId = @"1";
GetUserInfoApi *api = [[GetUserInfoApi alloc] initWithUserId:userId];
if ([api loadCacheWithError:nil]) {
? ? NSDictionary *json = [api responseJSONObject];
? ? NSLog(@"json = %@", json);
? ? // show cached data
? ? YTKJSONModel * model = [api responseJSONModel];
? ? NSLog(@"jsonmodelllll=%@---%@",model.nick,model.level);
}
api.animatingText = @"正在加載";
api.animatingView = self.view;
[api startWithCompletionBlockWithSuccess:^(YTKBaseRequest *request) {
? ? NSLog(@"update ui=%@",[api responseJSONModel]);
} failure:^(YTKBaseRequest *request) {
? ? NSLog(@"failed");
}];
}
大致步驟如此,只是這樣實(shí)現(xiàn)的話需要修改源代碼,細(xì)節(jié)可以參考 demo,地址:https://github.com/albertjson/YTKNetwork
2>token引發(fā)的問(wèn)題
一般情況下,網(wǎng)絡(luò)請(qǐng)求客戶端都要帶 token,用于服務(wù)端驗(yàn)證用戶的登陸有效性。那么 token 失效可能需要做一些處理,在 demo 中這部分驗(yàn)證是寫(xiě)在?ZCBaseRequest?類中實(shí)現(xiàn)的。這樣避免業(yè)務(wù)代碼在各處進(jìn)行處理 token 失效的情況
- (void)requestFailedFilter
{
? ? [super requestFailedFilter];
? ? if (error.code==TokenTimeOut) {
? ? ? ? ......
? ? }
}
當(dāng)然,這樣處理之后,如果子類需要在錯(cuò)誤的時(shí)候做特殊處理,那么在重寫(xiě)?requestFailedFilter?方法的時(shí)候一定要調(diào)用?[super requestFailedFilter]
3>錯(cuò)誤解析
YTKNetwork 調(diào)用 HTTP 返回錯(cuò)誤的類為 NSError。而自己的項(xiàng)目一般都需要定制錯(cuò)誤信息,或者根據(jù)某一類型的錯(cuò)誤進(jìn)行特殊的操作。這一步可以在自己定義的請(qǐng)求基類的錯(cuò)誤回調(diào)中處理。我們先來(lái)看一段 YTKNetwork 的源碼:
- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {
? ? //這里只留下關(guān)鍵性代碼
? ? NSError * __autoreleasing serializationError = nil;
? ? NSError * __autoreleasing validationError = nil;
? ? NSError *requestError = nil;
? ? BOOL succeed = NO;
? ? request.responseObject = responseObject;
? ? if ([request.responseObject isKindOfClass:[NSData class]]) {
? ? ? ? request.responseData = responseObject;
? ? ? ? request.responseString = [[NSString alloc] initWithData:responseObject encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];
? ? ? ? switch (request.responseSerializerType) {
? ? ? ? ? ? case YTKResponseSerializerTypeHTTP:
? ? ? ? ? ? ? ? // Default serializer. Do nothing.
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case YTKResponseSerializerTypeJSON:
? ? ? ? ? ? ? ? request.responseObject = [self.jsonResponseSerializer responseObjectForResponse:task.response data:request.responseData error:&serializationError];
? ? ? ? ? ? ? ? request.responseJSONObject = request.responseObject;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case YTKResponseSerializerTypeXMLParser:
? ? ? ? ? ? ? ? request.responseObject = [self.xmlParserResponseSerialzier responseObjectForResponse:task.response data:request.responseData error:&serializationError];
? ? ? ? ? ? ? ? break;
? ? ? ? }
? ? }
? ? if (error) {
? ? ? ? succeed = NO;
? ? ? ? requestError = error;
? ? } else if (serializationError) {
? ? ? ? succeed = NO;
? ? ? ? requestError = serializationError;
? ? } else {
? ? ? ? succeed = [self validateResult:request error:&validationError];
? ? ? ? requestError = validationError;
? ? }
? ? //只留關(guān)鍵性代碼
}
不難發(fā)現(xiàn),這里的錯(cuò)誤其實(shí)分三類
requestError:請(qǐng)求錯(cuò)誤,為 AFNetworking 進(jìn)行網(wǎng)絡(luò)請(qǐng)求的請(qǐng)求錯(cuò)誤,比如說(shuō)沒(méi)網(wǎng)絡(luò)。
serializationError:響應(yīng)錯(cuò)誤,為 AFNetworking 響應(yīng)錯(cuò)誤,比如返回的json數(shù)據(jù)你卻用了xml解析,還有很多情況等等。
validationError:校驗(yàn) json 錯(cuò)誤,這里包括?[request statusCodeValidator]?和?[request jsonValidator?兩種類型的錯(cuò)誤,前者為返回的 statusCode 不在你指定的成功請(qǐng)求區(qū)間內(nèi),后者為返回的 json 數(shù)據(jù) 跟你重載的 jsonValidator 函數(shù)中存在字段不一致的情況。
[可選] 如果你用 JSONModel 還會(huì)有 JSONModel 解析錯(cuò)誤產(chǎn)生的錯(cuò)誤。
處理方式如下:
//這里暫不考慮JSONModel解析錯(cuò)誤的問(wèn)題
- (void)requestFailedPreprocessor
{
? ? //note:子類如需繼承,必須必須調(diào)用 [super requestFailedPreprocessor];
? ? [super requestFailedPreprocessor];
? ? NSError * error = self.error;
? ? if ([error.domain isEqualToString:AFURLResponseSerializationErrorDomain])
? ? {
? ? ? ? //AFNetworking處理過(guò)的錯(cuò)誤
? ? }else if ([error.domain isEqualToString:YTKRequestValidationErrorDomain])
? ? {
? ? ? ? //猿題庫(kù)處理過(guò)的錯(cuò)誤
? ? }else{
? ? ? ? //系統(tǒng)級(jí)別的domain錯(cuò)誤,無(wú)網(wǎng)絡(luò)等[NSURLErrorDomain]
? ? ? ? //根據(jù)error的code去定義顯示的信息,保證顯示的內(nèi)容可以便捷的控制
? ? }
}
這里還有一種特殊情況,就是服務(wù)端返回的錯(cuò)誤不一定是以?錯(cuò)誤?的方式給你??赡苷?qǐng)求狀態(tài)碼依然是200OK,那么這個(gè)時(shí)候需要重寫(xiě) YTK 提供的成功和失敗的block和重寫(xiě)代理
4>loading動(dòng)畫(huà)以及錯(cuò)誤彈出機(jī)制
YTK自帶了一套插件機(jī)制,用于處理?YTKBaseRequest,YTKBatchRequest,YTKChainRequest?這幾種請(qǐng)求的loading展示機(jī)制,只需要傳入?animatingView?和?animatingText?即可。對(duì)于彈出統(tǒng)一的錯(cuò)誤提示,可以在?ZCBaseRequest?的失敗主線程回調(diào)中進(jìn)行。即:
///? Called on the main thread when request failed.
- (void)requestFailedFilter
{
? ? [super requestFailedFilter];
? ? if (![self isHideErrorToast]) {
? ? ? ? UIWindow * window = [[UIApplication sharedApplication] keyWindow];
? ? ? ? UIViewController * controller = [self findBestViewController:window.rootViewController];
? ? ? ? [WMHUDUntil showFailWithMessage:self.error.localizedDescription toView:controller.view];
? ? }
}
其中?[self isHideErrorToast]?用于表示是否隱藏錯(cuò)誤提示。該方法由具體的子類去實(shí)現(xiàn)。
5>網(wǎng)絡(luò)請(qǐng)求的終止
YTK給出網(wǎng)絡(luò)請(qǐng)求關(guān)閉方案:在dealloc中調(diào)用:
Remove self from request queue and cancel the request.
- (void)stop;
所以,建議每個(gè)網(wǎng)絡(luò)請(qǐng)求都在controller寫(xiě)成全局的變量。
下面展示一下具體某個(gè)請(qǐng)求的代碼:
ZCTYKTestViewController.m
@interface ZCTYKTestViewController ()
@property (nonatomic,strong) ZCMeGetInfoManger * infoManger;
@end
@implementation ZCTYKTestViewController
- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? [self setupData];
}
- (void)setupData
{
? ? self.infoManger = [[ZCMeGetInfoManger alloc] init];
? ? self.infoManger.animatingView = self.view;
}
- (IBAction)buttonAction:(UIButton*)sender
{
? ? [self clearTextView];
? ? ZCGetInfoParam * param = [[ZCGetInfoParam alloc] init];
? ? param.userId = @"0";
? ? param.token = @"222222";
? ? _infoManger.param = param;
? ? [_infoManger startWithCompletionBlockWithSuccess:^(__kindof YTKBaseRequest * _Nonnull request) {
? ? ? ? NSLog(@"responseJSONObject=%@",_infoManger.responseJSONObject);
? ? ? ? ZCGetInfoModel * infoModel = [[ZCGetInfoModel alloc] initWithDictionary:_infoManger.responseJSONObject error:nil];
? ? ? ? [self updateTextViewWithLog:[NSString stringWithFormat:@"讀取數(shù)據(jù):\n%@",infoModel]];
? ? } failure:^(__kindof YTKBaseRequest * _Nonnull request) {
? ? ? ? [weakself updateTextViewWithLog:[NSString stringWithFormat:@"讀取失敗:\n%@",weakself.infoManger.error]];
? ? }];
}
原文:http://www.360doc.com/content/18/0418/18/54658302_746699386.shtml