然而在我这边,我的意见是以Delegate为主,Notification为辅。原因如下:
- 统一回调方法,便于调试和维护
- 在跟业务层对接的部分只采用一种对接手段(在我这儿就是只采用delegate这一个手段)限制灵活性,以此来交换应用的可维护性
我分享这个问题的目的并不是想强调Notification多么多么不好,Notification本身就是一种设计模式,在属于他的问题领域内,Notification是非常好的一种解决方案。但我想强调的是,对于网络层这个问题领域内来看,架构师首先一定要限制代码的影响范围,在能用影响范围小的方案的时候就尽量采用这种小的方案,否则将来要是有什么奇怪需求或者出了什么小问题,维护起来就非常麻烦。因此Notification这个方案不能作为首选方案,只能作为备选。
那么Notification也不是完全不能使用,当需求要求跨层时,我们就可以使用Notification,比如前面提到的网络条件切换,而且这个需求也是需要满足一对多的。
block会给内部所有的对象引用计数加一,这一方面会带来潜在的retain cycle,不过我们可以通过Weak Self的手段解决。另一方面比较重要就是,它会延长对象的生命周期。
在网络回调中使用block,是block导致对象生命周期被延长的其中一个场合,当ViewController从window中卸下时,如果尚有请求带着block在外面飞,然后block里面引用了ViewController(这种场合非常常见),那么ViewController是不能被及时回收的,即便你已经取消了请求,那也还是必须得等到请求着陆之后才能被回收。
block和delegate乍看上去在作用上是很相似,但是关于它们的选型有一条严格的规范:当回调之后要做的任务在每次回调时都是一致的情况下,选择delegate,在回调之后要做的任务在每次回调时无法保证一致,选择block。在离散型调用的场景下,每一次回调都是能够保证任务一致的,因此适用delegate。这也是苹果原生的网络调用也采用delegate的原因,因为苹果也是基于离散模型去设计网络调用的,而且本文即将要介绍的网络层架构也是基于离散型调用的思路去设计的。
在集约型调用的场景下,使用block是合理的,因为每次请求的类型都不一样,那么自然回调要做的任务也都会不一样,因此只能采用block。AFNetworking就是属于集约型调用,因此它采用了block来做回调。
startRequest
这样的方法,它就会去根据这些参数起飞去调用API了,然后获得API数据之后再根据指定的着陆点去着陆。
我见过非常多的App的网络层在拿到JSON数据之后,会将数据转变成对应的对象原型。注意,我这里指的不是NSDictionary,而是类似Item这样的对象。这种做法是能够提高后续操作代码的可读性的。在比较直觉的思路里面,是需要这部分转化过程的,但这部分转化过程的成本是很大的,主要成本在于:
- 数组内容的转化成本较高:数组里面每项都要转化成Item对象,如果Item对象中还有类似数组,就很头疼。
- 转化之后的数据在大部分情况是不能直接被展示的,为了能够被展示,还需要第二次转化。
- 只有在API返回的数据高度标准化时,这些对象原型(Item)的可复用程度才高,否则容易出现类型爆炸,提高维护成本。
- 调试时通过对象原型查看数据内容不如直接通过NSDictionary/NSArray直观。
- 同一API的数据被不同View展示时,难以控制数据转化的代码,它们有可能会散落在任何需要的地方。
- 要点1:reformer是一个符合ReformerProtocol的对象,它提供了通用的方法供Manager使用。
- 要点2:API的原始数据(JSON对象)由Manager实例保管,reformer方法里面取Manager的原始数据(manager.rawData)做转换,然后交付出去。
- 要点3:例子中举的场景是一个API数据被多个View使用的情况,体现了reformer的一个特点:可以根据需要改变同一数据来源的展示方式。比如API数据展示的是“附近的小区”,那么这个数据可以被列表(XXXView)和地图(YYYView)共用,不同的view使用的数据的转化方式不一样,这就通过不同的reformer解决了。
- Controller的代码简洁
reformer机制能够带来以下好处:
-
好处1:绕开了API数据原型的转换,避免了相关成本。
-
好处2:在处理单View对多API,以及在单API对多View的情况时,reformer提供了非常优雅的手段来响应这种需求,隔离了转化逻辑和主体业务逻辑,避免了维护灾难。
-
好处3:转化逻辑集中,且将转化次数转为只有一次。使用数据原型的转化逻辑至少有两次,第一次是把JSON映射成对应的原型,第二次是把原型转变成能被View处理的数据。reformer一步到位。另外,转化逻辑在reformer里面,将来如果API数据有变,就只要去找到对应reformer然后改掉就好了。
-
好处4:Controller因此可以省去非常多的代码,降低了代码复杂度,同时提高了灵活性,任何时候切换reformer而不必切换业务逻辑就可以应对不同View对数据的需要。
-
好处5:业务数据和业务有了适当的隔离。这么做的话,将来如果业务逻辑有修改,换一个reformer就好了。如果其他业务也有相同的数据转化逻辑,其他业务直接拿这个reformer就可以用了,不用重写。另外,如果controller有修改(比如UI交互方式改变),可以放心换controller,完全不用担心业务数据的处理。
要达到第一个目的其实很简单,服务端需要给你一个密钥,每次调用API时,你使用这个密钥再加上API名字和API请求参数算一个hash出来,然后请求的时候带上这个hash。服务端收到请求之后,按照同样的密钥同样的算法也算一个hash出来,然后跟请求带来的hash做一个比较,如果一致,那么就表示这个API的调用者确实是你的APP。为了不让别人也获取到这个密钥,你最好不要把这个密钥存储在本地,直接写死在代码里面就好了。另外适当增加一下求Hash的算法的复杂度,那就是各种Hash算法(比如MD5)加点盐,再回炉跑一次Hash啥的。这样就能解决第一个目的了:确保你的API是来自于你自己的App。
一般情况下大部分公司不会出现需要满足第二种情况的需求,除非公司开发了自己的API平台给第三方使用。这个需求跟上面的需求有一点不同:符合授权的API请求者不只是一个。所以在这种情况下,需要的安全机制会更加复杂一点。