非常感谢Casa老师的文章,让我长久以来的很多疑惑,很多看了又忘的知识得到了系统的整理和总结。文章很容易让我建立起自己的记忆模型。就我的感觉,作为iOS程序员,把这样的文章读上几十遍都不为过,有种醍醐灌顶的痛彻。其中还有一个很重要的点,作为架构师是为工程师服务的,这种想法尤其的纯粹,放到工程师身上,如果也想着自己的代码是给别人读的,自己的产品是给用户用的,相信世界将变得更美好,我们离梦想也会更近。
使用哪种交互模式来跟业务层做对接? -- 什么方式,什么样的数据
是否有必要将API返回的数据封装成对象然后再交付给业务层?
使用集约化调用方式还是离散型调用方式去调用API?
针对链接建立环节的优化
针对链接传输数据量的优化
针对链接复用的优化
1. 网络层跟业务对接部分的设计
(1)使用哪种交互模式对接?
<1> 什么方式交付数据
- 主delegate,辅Notification(当需求要求跨层时,如网络条件切换)
分层架构的目的其中之一就在于下层对上层有一次抽象,让上层可以不必关心下层而执行自己的业务。
- 网络层然后尽量不要用block
(1)block很难追踪,难以维护
block() // 根本不知道这个block干了什么
(2)block会延长相关对象的生命周期
潜在的retain cycle,延长生命周期
平时尽量不要滥用block,尤其是网络层。
统一回调方法,便于调试和维护
block无法做到回调方法的统一,调试和维护的时候也很难在调用栈上显示出来,找的时候会很蛋疼。
在网络请求和网络层接受请求的地方时,使用block没问题。但是在获得数据交给业务方时,最好还是通过delegate去通知到业务方。因为block所包含的回调代码跟调用逻辑在同一个地方,会导致那部分代码变得很长,因为这里面包括了调用前&后的逻辑。从另一个角度来说,在一定程度上违背了single function,single task的原则,在需要调用API的地方,就只要写API调用相关的代码,在回调的地方写回调的代码。
Block是目前大部分第三方网络库都采用的方式,因为在发送请求的那一部分,使用Block能够比较简洁,因此在请求那一层是没问题的。只是在交换数据之后,转变成delegate比较好。
尽可能通过Delegate的回调方式交付数据,这样可以避免不必要的跨层访问。当出现跨层访问的需求时(比如信号类型切换),通过Notification的方式交付数据。正常情况下应该避免使用Block。
<2> 交付什么样的数据给业务层?
很多拿到JSON会转变成对象模型?
reformer过滤 -- 是对数据转化逻辑的一个封装
-- 保持NSDictionary数据可读性
NSString *const kPropertyListDataKeyID = @"kPropertyListDataKeyID";
设计初衷:通过reformer转换出来的可以直接是View,或者是view直接可以使用的对象(包括NSDictionary)。
总结:View + APIManager = reformer,NSDict + const字符串key
对于业务层而言,由Controller根据View和APIManager之间的关系,选择合适的reformer将View可以直接使用的数据(甚至reformer可以用来直接生成View)转换好之后交付给View。对于网络层而言,只需要保持住原始数据即可,不需要主动转化成数据原型。然后数据采用NSDictionary + Const字符串key 来表征,避免了使用对象来表征带来的迁移困难,同时不失去可读性。
(2)集约型 和 离散型 API调用方式?
<1> 集约型API,就是所有API调用只有一个类,这个类接收API名字,API参数和回调着陆点作为参数。
[APIRequest startReqWithApiName:@"name" params:params success:@selector(success:) fail:@selector(fail:) target:self];
<2> 离散型API,一个API对应一个APIManager,只要参数。API名字、着陆点都集成在APIManager中。
@property (nonatomic, strong) ItemListAPIManager *itemListAPIManager;
// getter方法初始化
[self.itemListAPIManager loadDataWithParams:params];
<3> 单看下层,都是集约型。因为发起一个API请求之后,除了业务相关(参数和API名字),剩下的都是统一处理的:加密,URL拼接,API请求的起飞和着陆。
业务层代码用离散型:
<1> 当前请求在外飞,不同业务需求有两种请求起飞策略:<1> 取消新发起的请求,等待外面飞的请求着陆(刷新页面的请求,刷新详情,刷新列表)。<2> 取消外面飞的请求,新发起的请求起飞(列表筛选,商品类型,价格区间)。--- 对于离散型API,编写不同的APIManager时候就可以针对不同的API设置不同的起飞策略。
<2> 便于针对某个API请求来进行AOP
<3> API请求的着陆点消失时,离散型能更好的处理
--- 页面的请求在外飞,用户点了back,然后VC被pop被回收。请求的着陆点就没了。容易crash。一般来说,处理这个情况都是在dealloc的时候取消当前页面所有的请求。(集约型) 但离散型可以写到APIManager里面。
总结:
对外提供一个BaseAPIManager来给业务方做派生,在BaseManager里面采用集约化的手段组装请求,放飞请求,然而业务方调用API时,以离散型调用。
如何做继承?
在APIManager情况下,最直觉就是BaseAPIManager提供一些空方法给子类重载。不好。可以用IOP来限制派生类的重载。
self.child = self; OR NSAssert(NO, "ERROR");
[self requestWithAPIName:[self.child apiMethodName] ...];
这么做好处是避免父类写空方法,约束子类。业务方实现子类,可以根据Protocol方法实现。
网络层与业务层对接总结:
(1) delegate数据连接,Notification跨层访问
(2) NSDictionary --> 业务层,用const字符串作Key保持可读性
(3) reformer处理网络层反馈数据
(4) 网络层上部分用离散型,下部分集约型
(5) 设计合理的继承机制,使派生APIManager受到限制
2. 网络层安全机制
(1)判断API调用请求来自于经过授权的APP
<1> 确保调用来自自己的APP,防止爬
设计签名:服务端给密钥,每次调用API时,使用,密钥+API名字+API请求参数 = hash,服务端按照相同方法生成比较。增加hash复杂度 = MD5...
<2> 对外提供需要注册才能使用的API平台,识别注册用户调用
较复杂,不做记忆
(2) 保证传输数据的安全
<1> 防止中间人攻击
<2> SPDY与HTTPS
HTTP/HTTPS SPDY 链接复用
3. 网络层的优化方案
(1)针对链接建立环节的优化
<1> 发起请求
-- 使用缓存手段减少请求的发起次数
什么时候清理缓存? 根据超时和缓存数据的大小
请求 -- 内存 -- 本地存储 -- 再发起请求
-- 策略减少请求的发起次数
i. 取消策略
刷新界面的请求和条件筛选的请求
ii. 用户日志的请求策略
在本地记录用户的操作记录,记录满30条发起一次请求将操作记录上传到服务器,然后,每次App启动的时候,上传一次上次遗留的没上传的记录。
<2> DNS域名解析得到IP
根据IP进行三次握手,链接建立成功
直接走IP请求,绕过DNS服务,尽可能不要用户使用对他来说很慢的IP方案为本地有一份IP列表,这些IP是所有提供API的服务器IP,每次app启动的时候,针对这个列表里的所有IP取ping延迟,选择最小的…
应用启动获取本地所有IP的ping,通过NSURLProtocol将URL中的HOST修改为我们找到的最快的IP
(2)针对链接传输数据量的优化
各种压缩
(3)针对链接复用的优化
SPDY & PipLine
维护一个队列
twitter-CocoaSPDY Voxer/iSPDY
有网络库的代码 PPT