如何在iOS中构建事件总线

232 阅读5分钟

「这是我参与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对象每次执行完thancatch方法后,这两个方法会返回之前的 Promise 对象,并且根据异步操作结果改变 Promise 对象的状态;

Promise 的三种状态:

pending: 异步事件执行中,状态可能进入下面的fulfilledrejected两者之一

fulfilled: 异步事件完成了,返回结果

rejected: 异步事件失败了,返回错误

关系图如下:

截屏2022-02-17 下午7.06.57.png

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 失败,会返回一个状态为 rejectedPromise 对象,返回的这个 Promise 对象会跳过后面所有的 then 方法直接执行 catch 方法。这就和事件总线中发布事件触发后,订阅事件会依次执行是一样的。

PromiseKit中还有一些方法,donewhenensure等等,讲讲它们大致的意思,

  • donedone 和 then含义是相同的,但是done不会返回 Promise对象
  • ensure:无论你的promise链的输出是成功或者失败,ensure回调永远会被执行
  • when:指定多个异步操作,等这些操作都执行完毕后才会执行 when 回调。when 方法类似 GCD 中的的 Dispatch Group,虽然实现的功能一样,但是使用起来也更加方便。

最后

当项目中的逻辑越来越复杂,各个模块不去做解耦,后期再想优化乃至重构都是一件困难的事,像平时开发中的数据层和UI层之间的交互如果不能及时使用这种解耦的方式,那后面真的是灾难性的,如果需求变动的频繁,整个人都像戴了痛苦面具一样。

在项目前期如果能够制定好代码的规范,以及模块粒度的标准,然后在使用规范的 Promise 接口将异步数据,业务,UI界面串联起来,那么后期在维护上相对容易很多;