YTKNetwork集成教程以及相关问题思考

DEMO

https://github.com/albertjson/SZCEvolution

YTKNetwork介绍

YTKNetwork 是猿题库 iOS 研发团队基于 AFNetworking 封装的 iOS 网络库,其实现了一套 High Level 的 API,提供了更高层次的网络访问抽象。目前在 GitHub 上已有 3600+ star ,是 Network 中的新星。

YTKNetwork提供的主要功能

支持按时间缓存和版本号缓存网络请求内容

支持统一设置服务器和 CDN 的地址

支持检查返回 JSON 内容的合法性

支持 block 和 delegate 两种模式的回调方式

支持批量的网络请求发送,并统一设置它们的回调(实现在 YTKBatchRequest 类中)

支持方便地设置有相互依赖的网络请求的发送,例如:发送请求 A,根据请求 A 的结果,选择性的发送请求 B 和 C,再根据 B 和 C 的结果,选择性的发送请求 D。(实现在 YTKChainRequest 类中)

支持网络请求 URL 的 filter,可以统一为网络请求加上一些参数,或者修改一些路径。

定义了一套插件机制,可以很方便地为 YTKNetwork 增加功能。猿题库官方现在提供了一个插件,可以在某些网络请求发起时,在界面上显示“正在加载”的 HUD。

YTKNetwork 的基本思想

YTKNetwork 的基本的思想是把每一个网络请求封装成对象。所以使用 YTKNetwork,你的每一个请求都需要继承 YTKRequest 类,通过覆盖父类的一些方法来构造指定的网络请求。

把每一个网络请求封装成对象其实是使用了设计模式中的 Command 模式,它有以下好处:

将网络请求与具体的第三方库依赖隔离,方便以后更换底层的网络库。

方便在基类中处理公共逻辑,例如猿题库的数据版本号信息就统一在基类中处理。

方便在基类中处理缓存逻辑,以及其它一些公共逻辑。

方便做对象的持久化。

当然,如果说它有什么不好,那就是如果你的工程非常简单,这么写会显得没有直接用 AFNetworking 将请求逻辑写在 Controller 中方便,所以 YTKNetwork 并不合适特别简单的项目。

关于集约式和离散式

集约式

介绍:即项目中的每个请求都会走统一的入口,对外暴露了请求的 URL 和 Param 以及请求方式,入口一般都是通过单例 来实现,AFNetworking 的官方 demo 就是采用的集约式的方式对网络请求进行的封装,也是目前比较流行的网络请求方式。

优点

使用便捷,能实现快速开发

缺点

对每个请求的定制型不够强

不方便后期业务拓展

离散式

介绍:即每个网络请求类都是一个对象,它的 URL 以及请求方式和响应方式 均不暴露给外部调用。只能内部通过 重载或实现协议 的方式来指定,外部调用只需要传 Param 即可,YTKNetwork就是采用的这种网络请求方式。

优点

URL 以及请求和响应方式不暴露给外部,避免外部调用的时候写错

业务方使用起来较简单,业务使用者不需要去关心它的内部实现

可定制性强,可以为每个请求指定请求的超时时间以及缓存的周期

缺点

网络层需要业务实现方去写,变相的增加了部分工作量

文件增多,程序包会变大[倒也不是特别大]

在微脉的iOS客户端,由于最初人员较少,且业务变更较频繁。故使用的就是集约式请求。不过考虑到为实现业务便捷性以及可拓展性,故增加了 RequestHeader 请求头,以及 WMHttpHelper 网络操作工具类。基本上已满足于目前的开发模式

不过长远来看,转成离散式的网络请求也是有必要的。

安装

你可以在 Podfile 中加入下面一行代码来使用 YTKNetwork

pod 'YTKNetwork'

集成至项目

项目文件介绍

YTKNetwork源码

YTKBaseRequest:为请求的基类,内部声明了请求的常用 API :

比如请求方式,请求解析方式,响应解析方式,请求参数等等。它的用意是让子类去实现的,本身不做实现。

YTKRequest:是 YTKBaseRequest 的子类,在其基础上支持了缓存,并且提供了丰富的缓存策略。基本上项目中使用都是继承于 YTKRequest 去写业务的 Request。

YTKNetworkAgent:真正做网络请求的类,在内部跟 AFNetworking 直接交互,调用了 AFNetworking 提供的各种请求,当然,如果底层想切换其他第三方,在这个类中替换掉就行了。

YTKNetworkConfig:该文件为网络请求的统一配置类,提供了设置 baseUrl cdnUrl 等基础请求路径,可以给所有的请求增加参数等等。

YTKBatchRequest:为批量进行网络请求而生,提供了代理和 block 两种方式给外部使用

YTKChainRequest:当多个请求之间有关联的时候采用此类去实现非常方便,即下一个请求可能要根据上个请求返回的数据进行请求。

YTKBatchRequestAgent,YTKChainRequestAgent:分别是 YTKBatchRequest,YTKChainRequest 的操作类,不需要也无妨主动调用

集成文件介绍

My Project Table

这是 Demo 工程的我新增的文件,一般情况下,不建议直接继承于 YTKRequest 类去写业务,需要自己写请求的基类,具体业务请求再继承于改项目基类,避免因新版本 YTKRequest 中修改了部分实现的默认值导致的程序需要做大量的修改。其中:ZCBaseRequest,ZCBatchRequest,ZCChainRequest 就是 demo 项目的基类。ZCJSONModel 是 JSON 转 Model 的基类,而 ZCHTTPError 是用于自定义错误信息的

single http example

这是 Demo 工程中具体某个请求的实例。这种展现方式很清晰,ZCGetInfoParam 是请求的入参类,ZCMeGetInfoManger是具体的请求操作类, ZCGetInfoModel是出参类。不过如果入参和出参很少,可以只有一个manger类

相关问题思考

我这里不想介绍 YTKNetwork 的基础和高级使用教程。如果想了解基础以及高级使用教程可以看这里

YTKNetwork 使用基础教程

YTKNetwork 使用高级教程

这篇文章重在介绍集成以及使用过程中遇到一些问题以及解决方案

1>JSON转Model的问题

对于稍微复杂的项目,可能某些接口返回数据有十多个,使用的时候不可能从字典中一个一个读取出来,然后再做  空处理,一般都是采用转 Model 的方式 转换成具体的业务模型,从业务模型中获取具体数据,常见的有 JSONModel,Mantle,MJExtension 等第三方库,本文以JSONModel为例,来实现框架内部解析成 Model

在 YTKBaseRequest 新增 JSONModel 属性

/// JsonModel类

@property (nonatomic, strong, readonly, nullable) id  responseJSONModel;

在 YTKBaseRequest 新增 modelClass 函数,用于子类去实现,表明要转换的具体 model 类的类名

YTKBaseRequest.h

/// model对应的类,子类实现的话会直接映射到该model类并进行初始化操作

- (Class)modelClass;

YTKBaseRequest.m

- (Class)modelClass

{

  return nil;

}

查看源码不难发现,真正处理网络请求成功和失败的地方是 YTKNetworkAgent 类,在 - (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error 和 - (void)requestDidSucceedWithRequest:(YTKBaseRequest *)request,- (void)requestDidFailWithRequest:(YTKBaseRequest *)request error:(NSError *)error 这三个方法。

具体操作为

- (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转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 {

    //这里不做处理,因为AFNetworking如果返回的数据为null的时候会调用失败的回调

}

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

{

  ///跟第二步的实现方式一样

}

到这里基本上已经实现了 json-model,具体的业务代码为:

- (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");

}];

}

大致步骤如此,只是这样实现的话需要修改源代码,细节可以参考 demo,地址:https://github.com/albertjson/YTKNetwork

2>token引发的问题

一般情况下,网络请求客户端都要带 token,用于服务端验证用户的登陆有效性。那么 token 失效可能需要做一些处理,在 demo 中这部分验证是写在 ZCBaseRequest 类中实现的。这样避免业务代码在各处进行处理 token 失效的情况

- (void)requestFailedFilter

{

    [super requestFailedFilter];

    if (error.code==TokenTimeOut) {

        ......

    }

}

当然,这样处理之后,如果子类需要在错误的时候做特殊处理,那么在重写 requestFailedFilter 方法的时候一定要调用 [super requestFailedFilter]

3>错误解析

YTKNetwork 调用 HTTP 返回错误的类为 NSError。而自己的项目一般都需要定制错误信息,或者根据某一类型的错误进行特殊的操作。这一步可以在自己定义的请求基类的错误回调中处理。我们先来看一段 YTKNetwork 的源码:

- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {

    //这里只留下关键性代码

    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;

    }

    //只留关键性代码

}

不难发现,这里的错误其实分三类

requestError:请求错误,为 AFNetworking 进行网络请求的请求错误,比如说没网络。

serializationError:响应错误,为 AFNetworking 响应错误,比如返回的json数据你却用了xml解析,还有很多情况等等。

validationError:校验 json 错误,这里包括 [request statusCodeValidator] 和 [request jsonValidator 两种类型的错误,前者为返回的 statusCode 不在你指定的成功请求区间内,后者为返回的 json 数据 跟你重载的 jsonValidator 函数中存在字段不一致的情况。

[可选] 如果你用 JSONModel 还会有 JSONModel 解析错误产生的错误。

处理方式如下:

//这里暂不考虑JSONModel解析错误的问题

- (void)requestFailedPreprocessor

{

    //note:子类如需继承,必须必须调用 [super requestFailedPreprocessor];

    [super requestFailedPreprocessor];

    NSError * error = self.error;

    if ([error.domain isEqualToString:AFURLResponseSerializationErrorDomain])

    {

        //AFNetworking处理过的错误

    }else if ([error.domain isEqualToString:YTKRequestValidationErrorDomain])

    {

        //猿题库处理过的错误

    }else{

        //系统级别的domain错误,无网络等[NSURLErrorDomain]

        //根据error的code去定义显示的信息,保证显示的内容可以便捷的控制

    }

}

这里还有一种特殊情况,就是服务端返回的错误不一定是以 错误 的方式给你。可能请求状态码依然是200OK,那么这个时候需要重写 YTK 提供的成功和失败的block和重写代理

4>loading动画以及错误弹出机制

YTK自带了一套插件机制,用于处理 YTKBaseRequest,YTKBatchRequest,YTKChainRequest 这几种请求的loading展示机制,只需要传入 animatingView 和 animatingText 即可。对于弹出统一的错误提示,可以在 ZCBaseRequest 的失败主线程回调中进行。即:

///  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] 用于表示是否隐藏错误提示。该方法由具体的子类去实现。

5>网络请求的终止

YTK给出网络请求关闭方案:在dealloc中调用:

Remove self from request queue and cancel the request.

- (void)stop;

所以,建议每个网络请求都在controller写成全局的变量。

下面展示一下具体某个请求的代码:

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:@"读取数据:\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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • YTKNetwork介绍 YTKNetwork 是猿题库 iOS 研发团队基于 AFNetworking 封装的 ...
    天清水蓝阅读 15,614评论 37 77
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • 写在前面: 这篇文写了什么?用YTKNetwork走一次网络请求的完整历程。 我能看到哪些内容?以下是YTKNet...
    RubyAhooo阅读 4,107评论 1 10
  • 也许你在人生的某个阶段,某一个瞬间会出现阴影!但那是因为你背后有阳光,我希望那个阳光是我!你就大踏步地朝前走,不管...
    绿色闪电阅读 350评论 1 1
  • 生活过得不太顺利的时候,写文字总是写得很顺利。慢慢地,写字成了我的一个寄托,不仅是心里苦到不行的时候可以做的事,也...
    yy日常阅读 437评论 0 0