网络是任何一个系统/平台的基础功能,在iOS上也同样是这样的.从NSURLConnection
到NSURLSession
,从ASIHttpRequest
到目前最主流的AFNetworking
.
事实上,一个项目中通常会有一个更高层次的封装.可以是自行基于NSURLConnection
进行封装,也可以基于AFNetworking
或者Alamofire
.
这里提供一个参考的姿势:ZCNetworking
结构
主要提供了3层
- 网络常用操作的抽象层:ZCNetworking,这里是基于
AFNetworking
- 针对项目的具体操作Runner层:ZCApiRunner;提供大量的功能,配置以简化项目中的实际使用.
- Actions:普通/上传/下载操作的封装;包括参数/url等,也提供了一些便利操作,比如好用的log.
抽象层
这是比较重要的一层,将网络操作和具体实现隔离开来.仅仅暴露出一些task.
有了这一层,那么替换基础库就成为可能.ZCNetworking是基于AFNetworking,或许有一天会修改成其他的networking或者是直接使用NSURLSession
呢?如果改动,只需要改动这一层即可,而整个项目不会受到任何影响.
这一层本身只提供几个基础方法:
- 数据获取: sendRequest
- 上传: uploadTask
- 下载:downloadFile;还包含了一个下载图片的方法
- 一些基础的操作方法,比如cookie等
基础方法中也提供了2种形式,以方便配置:包含NSURLSessionConfiguration
和不包含.当然不包含则是默认.
暴露的接口较少,当然完全可以根据实际需求进行扩充.不过需要确定的一点就是:接口完全和任意第三方类无关.这样才能使得替换底层实现与上层无关.
接口的实现没有太多要注意的.按照正常的AFNetworking
使用即可.
Runner
这一层是针对于项目的具体实现,做一些公共的配置/设置.包括:
- 区分正式/测试服务器
- 公共参数的处理,例如headers,params
- 对于逻辑成功/失败的处理
- 对于数据获取/上传/下载的处理
- 对于batch操作的处理
- 对于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中提供了相应的接口:addtionalHeaders
和globleParams
.
逻辑判定
基础网络只能够判定物理上是否成功.比如是否是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种方式:
- 断点
- 使用工具,例如charles.
不过断点不太方便,涉及到变量以及作用于的问题.针对个别问题还成,针对每一个请求都调试一翻,效率较低.
charles很好用,就是有点贵...
所以如果附带log信息的话,可能性价比较高.ZCNetworking
提供了一些log信息:
- url和参数,以xxx.com/action?a=xx&b=xx的方式拼接,对于部分get请求可以直接用浏览器调用.
- method/header/params
- 对于error的log,包括物理和逻辑上的
- 返回值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"即可.
只是希望讨论一个恰当的方式,实际上每个团队都会自己维护一套适合自己的网络库.合适自己项目约定的才是最好的.
才疏学浅,有不对的地方请指正.