网络层的参考姿势

网络是任何一个系统/平台的基础功能,在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"即可.

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

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

你可能感兴趣的:(网络层的参考姿势)