开发 iOS 应用时,总会遇到一次让人卡住的地方。
屏幕 A 发生的事情需要让远处的屏幕 B 知道,这时就会想:该如何连接它们?
不断传递代理,很容易让代码像意大利面一样纠缠。这时可以使用 Swift Pub-Sub 模式和事件总线。
今天就来整理 Swift Pub-Sub 模式与观察者模式的区别,以及事件总线到底是什么。
先说结论:观察者模式是“主体直接知道订阅者”的结构,而 Pub-Sub 在两者之间加入中介者(事件总线),让它们互不了解。
理解这句话后,其他内容就会顺畅很多。下面结合代码一步步来看。
先从观察者模式说起
观察者模式直接连接被观察的主体(Subject)和观察者(Observer)。
主体发生变化时,会直接向已注册的观察者发送通知。
如果你做过 iOS,应该已经使用过它。典型例子包括 NotificationCenter、Combine 的 @Published,以及 KVO(Key-Value Observing)。
关键在于,被观察的主体直接维护自己的订阅者列表。
// 被观察的主体直接管理订阅者
class Subject {
private var observers: [Observer] = []
func add(_ o: Observer) { observers.append(o) }
func notify() { observers.forEach { $0.update() } }
}
结构简单直观,非常适合一对多的 1:N 关系。
不过,随着规模扩大,让主体了解观察者的存在也会成为负担。
Swift Pub-Sub 模式有什么不同?
Pub-Sub 模式(发布-订阅模式)更进一步。
它会在发布者(Publisher)和订阅者(Subscriber)之间加入中介者。这个中介者就是事件总线,也可以称为消息代理。
发布者只需向总线发送“发生了这件事”,不需要知道谁会接收。
订阅者也只需向总线注册“这个事件到来时通知我”,不需要知道是谁发送的。
核心在于双方完全不知道彼此。这称为松耦合(decoupling)。
// 事件总线在发布者与订阅者之间进行中介
enum AppEvent { case userLoggedIn(id: String) }
final class EventBus {
static let shared = EventBus()
private var handlers: [(AppEvent) -> Void] = []
func subscribe(_ h: @escaping (AppEvent) -> Void) { handlers.append(h) }
func publish(_ e: AppEvent) { handlers.forEach { $0(e) } }
}
发布者只需一行EventBus.shared.publish(.userLoggedIn(id: "123"))即可完成。
登录界面甚至不需要知道主页存在。
观察者 vs Pub-Sub:表格快速对比
只听描述会觉得两者相似,所以用表格整理了区别。
| 分类 | 观察者模式 | Pub-Sub 模式 |
|---|---|---|
| 中介者 | 无(直接连接) | 有(事件总线) |
| 耦合度 | 主体知道订阅者 | 互不了解(松耦合) |
| 关系 | 主要是 1:N | 可以是 N:N |
| iOS 示例 | KVO, @Published |
NotificationCenter、事件总线 |
| 适用场景 | 观察特定对象的状态 | 远距离模块之间的通信 |
有意思的是,NotificationCenter实际上更接近 Pub-Sub。
虽然名称是“Notification”,但发布者和订阅者只能通过NotificationCenter这个总线相遇,不会直接引用彼此。
所以把“观察者模式 = NotificationCenter”这样记忆并不完全准确。从概念上看,它更接近 Pub-Sub。
那么,什么时候该用哪一种?
下面分享一下我在实际工作中采用的判断标准。
如果需要近距离观察一个对象的状态变化,观察者类方案很方便。Combine 的 @Published 和 SwiftUI 的 @Observable 就很合适。
反过来,如果要通知远距离模块之间,或应用全局发生的登录、支付完成、网络断开等重要事件,事件总线会整洁得多。
不过,事件总线也不是万能的。
滥用后会陷入“这个事件到底从哪里来的?”的困境,难以追踪流程。获得松耦合的代价,是可见性有所降低。
因此,我会用观察者(Combine)处理界面内部逻辑,只用事件总线处理跨越界面和模块的重要事件。
Q. 使用 Combine 后就不需要事件总线了吗?
不是。只用 Combine 的一个PassthroughSubject,就可以自己创建简单的事件总线。工具会重叠,但概念并没有被替代。
Q. Pub-Sub 总是更好的模式吗?
不一定。如果关系本身很紧密,不需要解耦,观察者模式更易读,也更方便调试。
观察者模式是直接连接,Pub-Sub 模式是通过中介者连接。区别就是这些。
两者不是竞争关系,而是根据场景选择的工具。下次设计界面时,先想想双方需要多了解彼此,很快就能判断哪种模式更合适。

