一、EventBus 是什么?
一句话:EventBus = 全局事件消息中转站 专门用来:跨页面、跨类、跨模块 解耦通信
最早是安卓经典三方库(GreenRobot EventBus)iOS 没有原生 EventBus,但是:苹果原生
NotificationCenter就是苹果自带的低配版 EventBus
解决什么痛点?
你平时通信有多恶心:
- A 页面 想通知 C 页面刷新
- 工具类想通知 ViewModel 弹窗
- 底层网络层报错,通知所有页面提示
常规写法:代理、闭包嵌套、单例属性传值、强引用耦合→ 代码乱、耦合严重、难维护
EventBus 终极方案:
我只管「发一条消息」谁需要监听,谁就「订阅这条消息」完全不用互相引用、不用传参数、零耦合
二、EventBus 核心三大角色(死记)
-
事件 Event自定义一个模型 / 标记,当做「消息暗号」比如:登录成功事件、退出登录事件、刷新列表事件
-
发布者 Publisher 任意地方(VC/VM/ 网络层 / 工具类)一行代码:
发送事件 -
订阅者 Subscriber需要接收消息的地方,提前注册监听收到事件自动回调执行业务代码
中间由 Bus 总线 统一转发、管理所有订阅。
三、完整工作流程
- 订阅者:注册监听「某某事件」
- 发布者:在任意位置,发送「某某事件」
- 总线收到消息,遍历所有监听该事件的对象
- 自动分发回调,订阅者执行业务
- 页面销毁 → 手动解除订阅(防内存泄漏、野指针)
四、EventBus 独有特色(安卓原版)
1. 自动线程切换
发消息在子线程,接收可以自动切主线程不用自己写 DispatchQueue.main
2. 粘性事件 Sticky(重点)
普通事件:先订阅,后发消息才能收到 粘性事件:先发消息,后面再订阅,也能收到上一条缓存消息
场景:启动页先发「用户登录状态」,首页后加载、后订阅,也能拿到之前的事件
3. 注解 + 反射(安卓)
通过注解标记接收方法,不用手动注册,iOS 不用这套
五、iOS 等价对照
| 技术 | 定位 | 级别 |
|---|---|---|
| EventBus (安卓) | 全局事件总线 | 强、功能全 |
| NotificationCenter | 苹果官方原生通知 | 弱、字符串硬编码 |
| 手写 自定义 EventBus | 泛型强类型总线 | 企业常用 |
| Combine PassthroughSubject | 局部数据流通信 | 页面内 / 模块内 |
核心区别(超级重要)
- NotificationCenter
- 基于 String 名字,弱类型,容易写错字符串崩溃
- 全局广播,到处都能发
- Combine
- 局部、强类型
- 适合单个 ViewModel 和 View 绑定,不适合全局跨模块
- EventBus(自定义)
- 强类型、泛型约束
- 全局跨模块解耦,比通知好用、安全
六、手写一个「极简 iOS 版 EventBus」
代码极少,你一眼看懂底层原理,和写 Combine 思路一模一样。
Swift
import Foundation
// 1. 定义事件协议(所有事件都遵守它)
protocol EventType {}
// 2. 自定义具体事件
struct LoginSuccessEvent: EventType
struct LogoutEvent: EventType
// 3. 全局 EventBus 总线
final class EventBus {
// 单例,全局唯一
static let shared = EventBus()
private init() {}
// 存储:[事件类型: 订阅回调数组]
private var eventDict: [Any.Type: [Any]] = [:]
// 订阅
func on<T: EventType>(_ type: T.Type, handler: @escaping (T) -> Void) {
eventDict[T.self, default: []].append(handler)
}
// 发送事件
func post<T: EventType>(_ event: T) {
guard let handlers = eventDict[T.self] else { return }
handlers.forEach { block in
if let action = block as? (T) -> Void {
action(event)
}
}
}
// 销毁订阅(防泄漏)
func clear<T: EventType>(_ type: T.Type) {
eventDict.removeValue(forKey: T.self)
}
}
使用
Swift
// 🔹 页面A:订阅监听登录事件
EventBus.shared.on(LoginSuccessEvent.self) { _ in
print("收到登录成功,刷新页面")
}
// 🔹 任意地方:比如网络层/登录页 发事件
EventBus.shared.post(LoginSuccessEvent())
✅ 完全解耦:发消息的人,完全不知道谁在监听收消息的人,完全不知道谁发的
七、EventBus / Notification / Combine 怎么选?
1. 用 EventBus(自定义)
- 跨页面、跨组件、跨底层工具类
- 需要强类型,不想用字符串通知
- 全局状态广播:登录、退出、版本更新、全局弹窗
2. 用 NotificationCenter
- 简单临时通知、小项目、快速开发
- 缺点:字符串硬编码、无类型校验
3. 用 Combine(@Published / Subject)
- 页面内部、ViewModel 和 View 通信
- 数据流连续变化(输入框、列表刷新、状态变更)
- MVVM 绑定首选
八、EventBus 致命坑(必记)
- 必须手动销毁订阅不然对象释放不了,严重内存泄漏
- 禁止滥用全局广播到处乱发事件,项目后期完全无法调试
- 多线程发送要加锁,防止数组崩溃
- 循环订阅导致死循环
九、结合之前的知识闭环
- 属性包装器 + Combine → 页面内 数据绑定、UI 刷新
- MVVM → 分层解耦
- EventBus → 全局跨模块 事件解耦
- Notification → 系统原生低配全局通信
十、一句话终极总结
- EventBus = 强类型版 全局通知中心
- 核心:发布 - 订阅模式,彻底解耦跨页面通信
- 局部用 Combine,全局用 EventBus / 通知
- 安卓标配 EventBus,iOS 项目一般自己手写轻量 EventBus 替代垃圾字符串通知