网络层的参考姿势

作者: oopp | 来源:发表于2016-04-21 21:57 被阅读3053次

    网络是任何一个系统/平台的基础功能,在iOS上也同样是这样的.从NSURLConnectionNSURLSession,从ASIHttpRequest到目前最主流的AFNetworking.

    事实上,一个项目中通常会有一个更高层次的封装.可以是自行基于NSURLConnection进行封装,也可以基于AFNetworking或者Alamofire.

    这里提供一个参考的姿势:ZCNetworking

    结构

    主要提供了3层

    1. 网络常用操作的抽象层:ZCNetworking,这里是基于AFNetworking
    2. 针对项目的具体操作Runner层:ZCApiRunner;提供大量的功能,配置以简化项目中的实际使用.
    3. Actions:普通/上传/下载操作的封装;包括参数/url等,也提供了一些便利操作,比如好用的log.

    抽象层

    这是比较重要的一层,将网络操作和具体实现隔离开来.仅仅暴露出一些task.

    有了这一层,那么替换基础库就成为可能.ZCNetworking是基于AFNetworking,或许有一天会修改成其他的networking或者是直接使用NSURLSession呢?如果改动,只需要改动这一层即可,而整个项目不会受到任何影响.

    这一层本身只提供几个基础方法:

    1. 数据获取: sendRequest
    2. 上传: uploadTask
    3. 下载:downloadFile;还包含了一个下载图片的方法
    4. 一些基础的操作方法,比如cookie等

    基础方法中也提供了2种形式,以方便配置:包含NSURLSessionConfiguration和不包含.当然不包含则是默认.

    暴露的接口较少,当然完全可以根据实际需求进行扩充.不过需要确定的一点就是:接口完全和任意第三方类无关.这样才能使得替换底层实现与上层无关.

    接口的实现没有太多要注意的.按照正常的AFNetworking使用即可.

    Runner

    这一层是针对于项目的具体实现,做一些公共的配置/设置.包括:

    1. 区分正式/测试服务器
    2. 公共参数的处理,例如headers,params
    3. 对于逻辑成功/失败的处理
    4. 对于数据获取/上传/下载的处理
    5. 对于batch操作的处理
    6. 对于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中提供了相应的接口:addtionalHeadersglobleParams.

    逻辑判定

    基础网络只能够判定物理上是否成功.比如是否是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种方式:

    1. 断点
    2. 使用工具,例如charles.

    不过断点不太方便,涉及到变量以及作用于的问题.针对个别问题还成,针对每一个请求都调试一翻,效率较低.

    charles很好用,就是有点贵...

    所以如果附带log信息的话,可能性价比较高.ZCNetworking提供了一些log信息:

    1. url和参数,以xxx.com/action?a=xx&b=xx的方式拼接,对于部分get请求可以直接用浏览器调用.
    2. method/header/params
    3. 对于error的log,包括物理和逻辑上的
    4. 返回值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"即可.

    只是希望讨论一个恰当的方式,实际上每个团队都会自己维护一套适合自己的网络库.合适自己项目约定的才是最好的.

    才疏学浅,有不对的地方请指正.

    相关文章

      网友评论

      • 黄二瓜:博主,提了一个issue,麻烦请看一下
        oopp:@黄二峰 好的,我明天来处理下.
      • 春田花花幼儿园:楼主你好,最近我在看你封装的这套,研究封装.在上传的使用ZCApiRunner的时候,用这个上传的方法:
        - (NSURLSessionUploadTask *)uploadAction:(ZCApiUploadAction *)action progress:(void (^) (NSProgress *uploadProgress))progress success:(ZCTypeBlock)success failure:(ZCErrorBlock)failure;

        其中ZCApiUploadAction这个属性我是这么定义的,但是运行时候有问题.我看到里边有domain这些参数是需要设置的,但是你的Demo例子里边没有使用的范例. 能补一下范例或者说说下使用的介绍吗? :blush:
        oopp:@春田花花幼儿园 因为不希望改变params这个对象,runner就是使用这个对象.但是对象里面的内容可以修改.而且也无须初始化,因为已经初始化好了.
        春田花花幼儿园:@oopp 感谢楼主,这里有个参数 ZCApiUploadAction.params这个参数.
        ZCApiUploadAction *upload = [[ZCApiUploadAction alloc] initWithURL:@"xxxx"];
        我直接赋值给他一个 NSMutableDictionary:
        upload.params = [NSMutableDictionary dictionary];
        报错说readOnly
        所以按照你说明中给出的那样:
        upload.params[@"111"] = @"222";

        这里,既然我们会对params赋值,那楼主为什么要还要把params属性设置成readOnly呢?
        oopp:@春田花花幼儿园 github已更新,做了一个简单的使用说明.
      • S_MG:你好,批量请求batchTasksWithActions使用GCD来实现,那请问如何取消批量请求呢?
        oopp:@S_MG 取消到和gcd无关.只需要返回批量的task的array,需要取消的话,就对array的task进行cancel即可.不过我这里没这么写,需要的话可以修改一下:)
      • 暮落晨曦:那HTTPS怎么办。。。。看内部实现好像没有针对HTTPS的哇= =?
        暮落晨曦:@oopp 嗯,辛苦
        oopp:@暮落晨曦 恩啊,没有针对https的处理.不过如果是ca的证书的话,那么本身就支持htts,无需做任何处理.如果是自建证书的话,确实需要支持.目前没有支持..因为本身我们目前证书是ca的..
      • 黄二瓜:这就是我想要的,几个地方都写到痛点了
      • zyg:能加一些 应用的场景+设计 那就完美啦~ 经验不足有时候也不知道设计的目的哈哈~ 我就是个菜鸟~

      本文标题:网络层的参考姿势

      本文链接:https://www.haomeiwen.com/subject/aantrttx.html