IOS项目结构优化设计之网络层封装

为什么要封装网络层

一个APP的核心是什么,是从网络获取相关数据,以一种友好的形式展现在用户面前。因此网络请求的优化就显得尤为重要。网络层在一个APP中承担了API调用,操作记录和崩溃反馈等重要的职能。苹果本身就已经将网络请求封装的很好了,而且目前网络上有很多极其优秀的第三方框架,类似的有AFNetworking等,目前大部分公司都是在此基础上进行适合自己项目结构的封装。

###不封装有什么隐患

13年刚开始做iOS的时候,基本都是些比较小的不够规范的小项目,首先是界面的要求不高,程序的效率不是需要考虑的因素,因此我是在每个控制器里写网络请求。这就给自己埋下了一个巨大的坑,当后台的接口重构,我需要去一个个找到相对应的控制器,去修改。当官方网络API更新了,也需要一个个去找。同时,不封装,项目代码将显得过于“肥胖”。因此,处于对整个项目的优化,并且提高以后的开发效率,封装网络请求显得尤为重要。

如何封装

一个完整的网络架构大致分为三个部分,服务器,接口请求类,控制器。

关于API

之前有提到因为API分散在项目里造成后期维护后台接口有变化之后,一个个去找的尴尬。在我们进行封装前的准备时,应当首先考虑创建一个头文件,将所有的URL全都丢进去,这样后期接口有变化时就不再需要一个个去找了。

数据下发方式

网络层拿到数据之后,要如何交付给业务层?之前有看过很多大牛的文章,手段众多,灵活可靠。有些使用block传值,有些使用notification,有的使用delegate来进行数据交付。我在项目里主要使用blcok方式。但参考了一些博文,似乎使用delegate更好些。原因大概如下(仅仅是搬运工):
1.尽可能减少跨层数据交流的可能,限制耦合

2.统一回调方法,便于调试和维护

限制耦合,减少跨层数据交流

notification的特性在于一对多,使用notification来进行网络层和业务层之间的数据交换,虽然非常方便,但是由于其一对多的特性,一旦某个地方出了问题,那将是整个项目的灾难,同时,注册过多地notification,也令项目难以维护。

之前在一篇博客上看过一个故事:曾经有一个工程师在监听Notification之后,没有写释放监听的代码,当然,找到这个原因又是很漫长的一段故事,现在找到原因了,然而监听这个Notification的对象有那么多,不知道具体是哪个Notificaiton,也不知道那个没释放监听的对象是谁。后来折腾了很久大家都没办法的时候,有一个经验丰富的工程师提出用hook(Method Swizzling)的方式,最终找到了那个没释放监听的对象,bug修复了。

因此相比较notification,delegate就显得比较出色,能够很好完成业务需求,并且利于日后的维护。

关于block,它确实有它不好的地方。首先是内存管理方面,block会自动给内部的对象引用计数加一,一方面会有潜在的内存泄漏的危险,另一方面,它会延长对象的生命周期。而delegate本身是弱引用,执行之后可以被回收。

统一回调方法,便于调试和维护

在网络请求和网络层接受请求的地方时,使用Block没问题。但是在获得数据交给业务方时,最好还是通过Delegate去通知到业务方。因为Block所包含的回调代码跟调用逻辑放在同一个地方,会导致那部分代码变得很长,因为这里面包括了调用前和调用后的逻辑。从另一个角度说,这在一定程度上违背了single function,single task的原则,在需要调用API的地方,就只要写API调用相关的代码,在回调的地方,写回调的代码。

然后我看到大部分App里,当业务工程师写代码写到这边的时候,也意识到了这个问题。因此他们会在block里面写个一句话的方法接收参数,然后做转发,然后就可以把这个方法放在其他地方了,绕过了Block的回调着陆点不统一的情况。比如这样:


[API callApiWithParam:param successed:^(Response *response){
[self successedWithResponse:response];
} failed:^(Request *request, NSError *error){
[self failedWithRequest:request error:error];
}];

Block是目前大部分第三方网络库都采用的方式,因为在发送请求的那一部分,使用Block能够比较简洁,因此在请求那一层是没有问题的,只是在交换数据之后,还是转变成delegate比较好,比如AFNetworking里面:


[AFNetworkingAPI callApiWithParam:self.param successed:^(Response *response){
if ([self.delegate respondsToSelector:@selector(successWithResponse:)]) {
[self.delegate successedWithResponse:response];
}
} failed:^(Request *request, NSError *error){
if ([self.delegate respondsToSelector:@selector(failedWithResponse:)]) {
[self failedWithRequest:request error:error];
}
}];

下发数据的处理

现在基本APP的网络数据都是以json格式的形式回传的,这样的数据必然需要一定的处理才可以给到业务层使用

以什么数据格式交付?

相信绝大部分人都是以NSDictonary的数据类型来存储数据然后回传给使用者,也有些是直接转化成对象格式然后传给使用者。这样做确实很省事,但是相应的也必须要付出代价。首先是转化的效率问题。如果回传的数据过大,或者说有几千数据的数组,,那么处理的效率自然不敢恭维,可能你的APP会卡在等待刷新的界面卡很久。

我在项目里回传了三个参数,一个是主要的参数info,普遍的NSDictionary类型,用于存放主要的数据,另外是code,状态码,用于表示接口请求的状态,成功、失败、挂起等,还有一个参数是errorinfo,用于存放错误信息,如果接口请求成功,那么errorinfo将为nil。

网络请求的优化

网络请求的优化是多方面的,不仅是开发人员从技术层次做的优化,还是从用户体验层次做的优化,都大有搞头。在阅读本章节之前,推荐先熟练掌握计算机网络相关的知识。

技术层面的优化:

基础的网络请求

1.减少请求带宽:目前主流的请求与响应体数据编码格式是xml和json,通常,使用json能提高网络请求的效率。
2.与响应压缩一样,客户端不应改将CPU时间浪费在压缩如PDF,加密数据,图像,音频及视频等已经压缩的内容上,然而,代表预先压缩的数据的Base64数据常常会从请求压缩中获益。比如,如果要以Base64格式上传JPEG文件,那么可以对Base64数据进行压缩,相较于未压缩的Base64数据,压缩后的数据体积会降低30%左右。

合理并发

有些页面会出现多个请求集中发起的情况,此时应当有一个合理的并发数量。并发数如果太小,会导致“劣质”的请求block住“优质”的请求。如果并发数太大,带宽有限的场景下,会增加请求的整体延迟。

用户体验方面的优化:

用户体验层的优化,主要是在APP可靠性方面的优化

可靠性优化:

可靠性保障也是个容易被忽视的方面,在深入探讨之前,可以先将Request按业务属性分类。

第一类:关键核心的业务数据,期望能100%送达服务器。
第二类:重要内容请求,需要较高的请求成功率。
第三类:一般性内容请求,对成功率无要求。
之所以要将请求分为三类,是要在可靠性保障上做区分。理论上我们应该尽可能让所有的请求成功率达到最高,但客户端的流量,带宽,手机电量,服务器的压力等都是有限的资源,所以我们采取的策略是只对关键性的网络请求做高强度的可靠性保障。

第一类请求类似大家用微信时发送的消息,消息数据一旦从输入框发出,从用户来的角度感知这个消息数据是一定会到达对方的。如果网络环境差,网络模块会自动在后头悄悄重试,一段时间后仍无法成功就通过产品交互的方式告知用户发送失败了,即使失败,请求的数据(消息本身)一直存在客户端。

对于这类请求的处理方式第一步不是通过网络发送,而是持久化到Database当中。一旦入了DB,即使断网,断电,重启,请求数据依然还在,只需在App重启的时候还原请求数据,再次发送即可。我们用代码来进一步阐释。

网络请求的加密环节

暂未更新

数据格式的转换(字典转模型)

暂未更新

缓存处理

暂未更新

最后说一句

不喜勿喷

本文总阅读量