设计事件总线
问题
如何设计 iOS 端的事件总线(Event Bus)系统?
答案
基于 Combine 实现
import Combine
class EventBus {
static let shared = EventBus()
private var subjects: [String: Any] = [:]
private let lock = NSLock()
// 发送事件
func post<T>(_ event: T) {
let key = String(describing: T.self)
lock.lock()
let subject = subjects[key] as? PassthroughSubject<T, Never>
lock.unlock()
subject?.send(event)
}
// 订阅事件
func on<T>(_ type: T.Type) -> AnyPublisher<T, Never> {
let key = String(describing: T.self)
lock.lock()
defer { lock.unlock() }
if let subject = subjects[key] as? PassthroughSubject<T, Never> {
return subject.eraseToAnyPublisher()
}
let subject = PassthroughSubject<T, Never>()
subjects[key] = subject
return subject.eraseToAnyPublisher()
}
}
使用
// 定义事件
struct LoginEvent {
let userId: String
}
// 订阅
cancellable = EventBus.shared.on(LoginEvent.self)
.receive(on: DispatchQueue.main)
.sink { event in
print("User logged in: \(event.userId)")
}
// 发送
EventBus.shared.post(LoginEvent(userId: "123"))
方案对比
| 方案 | 类型安全 | 生命周期管理 | 适用场景 |
|---|---|---|---|
| NotificationCenter | ❌ | 手动 removeObserver | 系统通知 |
| Combine EventBus | ✅ | Cancellable 自动释放 | 业务事件 |
| Delegate/Closure | ✅ | 1 对 1 | 组件间通信 |
最佳实践
EventBus 适用于松耦合的跨模块通信(如登录成功通知多个模块刷新)。组件内部优先用 Delegate/Closure,更容易追踪数据流。
常见面试问题
Q1: EventBus 和 NotificationCenter 有什么区别?
答案:NotificationCenter 基于字符串 name,无类型安全,传值通过 userInfo 字典需要强转。Combine EventBus 基于泛型类型分发,编译期检查类型,且 Cancellable 不需要手动 removeObserver,更安全。