前言
客户端网络层交互流程:
- 获取参数,统一配置
- 根据API配置公共参数
- 构造网络请求
- 发送网络请求
- 获取返回结果
- 展示数据
注:此处还应该有个 数据持久化
流程,因项目业务层逻辑及存储方案各不相同,本篇中不展开介绍
另:本篇幅中暂时只涉及到HTTP的请求,TCP及UDP的网络交互处理又不一样,需要考虑的也不相同(如:TCP考虑包顺序处理及回调分发问题,UDP考虑丢包及无序处理)
记录: iOS TCP链接使用
GCDAsyncSocket
库;
UDP简单实现丢包处理方法,给文件分块,每个数据包的头部添加一个唯一标识序号的ID值,当接收到的包头部ID不是期望中的ID号,则判定丢包,将丢包ID发回服务端,服务端接收到丢包响应则重发丢失的数据包
UDP模拟TCP协议三次握手,这样对丢包处理有帮助
本文是博主15年读casa中网络层设计方案的记录与思考总结。
聊聊AFN
AFN2.x特性:
- 使用NSOperation,支持取消,不需占用完整线程(只在一个线程等待)
- 通过NSOperationQueue来进行并发任务数量限制(maxConcurrentOperationCount)
- 可暂停,可添加依赖
- 安全性设置(HTTPS、AFURLConnectOperationSSLPinningMode)
- HTTP缓存(NSURLCache,数据缓存在本地sqlite里,需要服务器配合,设置请求头部信息)
注:在等待请求时只有一个Thread,在这个Thread上启动一个runloop监听NSURLConnection的NSMachPort类型源。start里面直接跳转到这个线程执行,在加入NSOperationQueue时,顶多start方法执行的时候占用一个线程,然后真正的发送请求和等待都是在这个NetworkRequestThread里面进行的(而最终访问网络并拉取数据不是这个线程,是NSURLConnection统一调度)。
网络交互中可以做哪些
流程项 | 可操作项 |
---|---|
获取参数,统一配置 | 进行参数验证 |
根据API配置公共参数 | 组件化配置HTTP头部、cookie、签名等 |
构造请求 | 设置拦截器进行拦截请求 |
发出请求 | 对已发出请求记录ID,统一管理(缓存) |
返回结果 | 设置拦截器进行网络请求返回拦截,验证返回数据正确性、缓存数据 |
展示数据 | 根据API及业务层展示的需要,使用reformer将数据转化成任何你想要的东西(NSDictionary、DataList、View) |
1.参数验证
- 注册账号或者发货信息等有必填选项,避免调用API产生不必要的开销
- 代码即文档,只需看这个验证函数就知道需要传什么参数
2.组件化
- 使用工厂模式生产出不同的服务,每个服务配置好自己的线上线下url及版本号等信息(方便灵活),独立的组件来解耦API调用逻辑生产各种参数
3.根据不同API的配置使用2种策略
- 正在请求时,忽略新来的请求(当滚动tableview时,会频繁触发加载下一页的事件,如果当前APIManager正在加载下一页,那么就不需要再发送加载请求)
- 正在请求时,取消过去已发送的请求,执行现在请求(查询商品,切换筛选条件时,如果前一次筛选条件的请求正在进行中,那么就应当取消前一次请求,执行现在的请求 — 考虑到正在进行的请求取消设计)
4.通过将NSOperation保存requestID成NSMutableDictionary,随时依照requestID查找请求并支持取消
5.缓存
- 接口返回的数据很少变动,不希望做重复请求
- 网络慢或者服务器等异常状况容灾
引申:对于一些点赞和取消 频繁与网络交互的设计考虑
网络层与业务层对接部分设计
1.使用哪种交互模式与业务层做对接
- 以什么方式将数据交付给业务层
a.以Delegate为主,Notification为辅;尽可能减少跨层数据交流的可能,限制耦合;统一回调方法,便于调试和维护;在跟业务层对接的部分只采用一种对接方式,限制灵活性,以此来交换应用的可维护性(delegate对上下文有限制性)。
b.在网络请求和网络层接受请求的地方,使用Block没问题,但在获得数据交给业务方时,最好还是通过Delegate去通知到业务方。 - 交付什么样的数据给业务层
选择合适的reformer将View可以直接使用的数据(甚至reformer可以用来直接生成View)转化好之后交付给View;对于网络层而言,只需要保持住原始数据即可,不需要主动转化成数据model,数据采用NSDictionary加const字符串key来表征,避免了使用model来表征带来的迁移困难,同时不失去可读性
2.是否有必要将API返回的数据封装成对象然后再交付给业务层
1 | 先定义一个protocol: |
a.reformer是一个符合ReformerProtocol的对象,它提供了通用的方法供Manager使用;
b.API的原始数据(JSON对象)由Manager实例保管,reformer方法里面取Manager的原始数据(manager.rawData)做转换,然后交付出去。就比方是:莲蓬头的水管部分是Manager,负责提供原始水流(数据流),reformer就是不同的漏斗模式,换什么reformer就能出来什么形式的水(是不是有一种RAC信号的感觉)。
c.例子中举的场景是一个API数据被多个View使用的情况,体现了reformer的一个特点:可以根据需要改变同一数据来源的展示方式。比如API数据展示的是“附近的小区”,那么这个数据可以被列表(XXXView)和地图(YYYView)共用,不同的view使用的数据转化方式不一样,这就通过不同的reformer解决了。
3.使用集约型调用方式还是离散型方式调用API
建议方式:对外提供一个BaseAPIManager来给业务方做派生,在BaseManager里面采用集约型的手段,加密处理、URL拼接、组装请求和放飞请求,然而业务方调用API时,则是以离散的API方式来调用。
实际上,在博主之前做的项目中,账号系统及充值计费系统是属于集约型调用方式,主要是需求简单,调用接口多,返回数据处理逻辑简单。
离散型单独对某个API请求的起飞和着陆过程可以进行AOP拦截,做到参数验证及返回数据缓存等操作,reformer机制就是基于离散型的API调用方式的。
设计代码层面
1.APIBaseManager存在的意义
1 | BaseAPIManager的init方法里这么写: |
将子类的继承方式标准化
<APIManager>
interceptor 同时支持外部拦截和内部拦截(TODO:说明内部拦截和外部拦截区别)
提供撤销网络请求的方法
获得数据后,使用reformer转化成图片、语音以及任何你希望得到的东西
可配置缓存
它是衔接业务逻辑和底层API调用的一个组件,其派生出来的各种APIManager在各个APP上都可重用、可移植,方便代码管理
blabla:同一个API不用重复写同一段调用代码和回调取数据逻辑,提高代码的组件化程度和集成度;Manager和Controller之间的关系可以不必非常紧密,切换API非常方便,降低了不必要的耦合(属于Model层)2.使用reformer(外观模式)
a.在处理单View对多API,以及在单API对多View的情况时,reformer提供了非常优雅的手段来响应这种需求,隔离了转化逻辑和主体业务逻辑,避免了维护灾难。
b.转化逻辑集中,且将转化次数转为只有一次。使用数据原型的转化逻辑至少有两次,第一次把json映射成对应model,第二次把model变成能被View处理的数据。而reformer一步到位。
c.业务数据和业务有了适当的隔离,将来如果业务逻辑有修改,换一个reformer改掉就好;若其他业务也有相同的数据转化逻辑,其他业务直接拿着这个reformer就可以用了,不需要重写。
网络层优化方案
- 安全机制
- 请求优化(缓存,策略)
- DNS缓存映射
- 链接复用(SPDY)
还可以做哪些?
- 请求重发机制
- 批量网络请求发送,并统一设置他们的回调
- 设置有相互依赖的网络请求发送
扩展提问:网络部分token过期,如何处理?
遇到token失效就扔个Notification出去。XAPIManager请求失败(token失效,且在BaseApiManager中判断)后,扔出通知,将通知的object设为当前失败的这个Manager也就是XManager。然后中间人收到通知,记录下随着Notification过来的Object,也就是失败的那个Manager。然后在当前ViewController中present登陆页面。中间人收到Token刷新成功的回调,拿出刚才拿到的失败的那个Manager,直接调用[XManager loadData],顺便dismiss掉登录页面,就好了。参数会由XManager的paramSource提供,你只管调loadData就好了,paramSource的重要性就体现在这里了。回调什么的也在你上一次失败时的页面内,整个过程完全无缝,且基本没有耦合