「这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战」。
聊聊事件总线
其实事件总线是对发布和订阅模式的一种实现。它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖。通过发布和订阅可以将组件间一对一或者一对多的耦合关系解开。这种模式比较适用于数据层通过异步回调的方式发送数据给UI层订阅者,使得UI层和数据层可以不用耦合在一起。
我们常常在开发中会遇到一个异步接口请求之后,又会在接口回调中继续处理其他的接口,如果使用 Block 或者 Delegate 则会出现嵌套的现象,java中对这种现象有个说法叫做“回调陷阱(CallbackHell)”。而KVO是强依赖属性的,当属性的值发生变化就会发布给观察者,这种现象有些时候并不是我们想要的,太过于灵活,所以会难以维护,NSNotificationCenter需要通过硬编码字符串建立发布者和订阅者的关系,也同样存在过于灵活。那么在iOS中有什么方式适合构建事件总线的呢?答案是有,那就是Promise。
Promise
Promise 的概念最早是在 E 语言中被提出的。 ECMAScript 6 将其写入了语言标准,统一了用法,并提供了原生的 Promise 对象。随后,Homebrew 的作者 Max Howell 开发了 PromiseKit,将 Promise 标准带到了 iOS 中。
Promise模式会把每一个异步操作封装成一个Promise,这个Promise对象是异步操作执行完毕的结果。本质上这种模式通过Promise对象来保存异步操作,然后通过一个统一的异步回调接口来处理事件。下面我们一起看看Promise是怎么实现的。
Promise 的两个方法:
then: 指定的回调函数
catch: 指定发生错误的回调函数
then是整个Promise设计模式的核心,Promise对象每次执行完than、catch方法后,这两个方法会返回之前的 Promise 对象,并且根据异步操作结果改变 Promise 对象的状态;
Promise 的三种状态:
pending: 异步事件执行中,状态可能进入下面的fulfilled或rejected两者之一
fulfilled: 异步事件完成了,返回结果
rejected: 异步事件失败了,返回错误
关系图如下:
当 then 方法执行完返回 Promise 对象,能够继续同步执行多个 then 方法,如果执行 then 方法后返回的 Promise 对象是 rejected 状态的话,程序会直接执行 catch 方法。then 方法执行的就是订阅操作,Promise 对象触发 then 方法也就是事件总线中的发布操作,由此,实现了一个发布操作对应多个订阅事件。
了解以上Promise相关的知识,那么回到我们的正题中,我们来看看如何在iOS使用PromiseKit框架来构建事件总线。
PromiseKit
借助这个第三方库,我们也可以在iOS上不用再写那么多的回调嵌套了,摆脱了嵌套地狱的痛苦,同时还能让代码能加的整洁,规范。
下面我们举例来小试牛刀一把,我们可能会遇到这种的需求: 提交某些认证信息时,可能会先校验有没有权限,然后在上传照片,上传完成后将所有信息提交服务器;
按照我们以往的做法:
// 校验
[XXNetwork checkXXCompletion:^(NSURLSessionDataTask *task, ApiResponse *aResponse, NSError *anError) {
// 上传图片
[XXNetwork uploadImages:(NSArray *)images Completion:^(NSURLSessionDataTask *task, ApiResponse *aResponse, NSError *anError) {
// 上传信息
[XXNetwork uploadAuthInfoCompletion:^(NSURLSessionDataTask *task, ApiResponse *aResponse, NSError *anError) {
}];
}];
}];
但是使用PromiseKit是这样的:
[XXNetwork checkXX].then(^(NSDictionary *json){
return [XXNetwork uploadImages];
}).then(^(NSDictionary *json){
return [XXNetwork uploadAuthInfoCompletion];
}).then(^(NSDictionary *json){
// ...
}).catch(^(NSError *error){
[[UIAlertView …] show];
})
从以上的例子中可以看出,使用Promise的方法来处理异步请求操作,看起来就像是进行同步操作一样,调用的顺序和流程也一目了然。使用 then 方法可以让异步操作一个接着一个地按顺序进行。如果异步操作 uploadImages 失败,会返回一个状态为 rejected 的 Promise 对象,返回的这个 Promise 对象会跳过后面所有的 then 方法直接执行 catch 方法。这就和事件总线中发布事件触发后,订阅事件会依次执行是一样的。
在PromiseKit中还有一些方法,done 、when、ensure等等,讲讲它们大致的意思,
done:done和then含义是相同的,但是done不会返回Promise对象ensure:无论你的promise链的输出是成功或者失败,ensure回调永远会被执行when:指定多个异步操作,等这些操作都执行完毕后才会执行when回调。when 方法类似GCD中的的Dispatch Group,虽然实现的功能一样,但是使用起来也更加方便。
最后
当项目中的逻辑越来越复杂,各个模块不去做解耦,后期再想优化乃至重构都是一件困难的事,像平时开发中的数据层和UI层之间的交互如果不能及时使用这种解耦的方式,那后面真的是灾难性的,如果需求变动的频繁,整个人都像戴了痛苦面具一样。
在项目前期如果能够制定好代码的规范,以及模块粒度的标准,然后在使用规范的 Promise 接口将异步数据,业务,UI界面串联起来,那么后期在维护上相对容易很多;