製作 iOS App 時,總會遇到一次讓人卡住的地方。
畫面 A 發生的事情,遠處的畫面 B 需要知道;這時就會想:兩者要如何連接?
不斷傳遞 Delegate,很容易讓程式碼像義大利麵一樣糾結。這時就該拿出 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 就很適合。
相反地,如果要通知遠距模組之間,或 App 全域發生的登入、付款完成、網路中斷等事件,事件匯流排會俐落得多。
不過,事件匯流排也不是萬靈丹。
濫用後會陷入「這個事件到底從哪裡來?」的困境,難以追蹤流程。換來鬆耦合的代價,就是可見性稍微降低。
因此我會將畫面內的邏輯交給觀察者(Combine),只有跨越畫面與模組的重要事件才使用事件匯流排處理。
Q. 使用 Combine 就不需要事件匯流排了嗎?
不是。只要使用 Combine 的 PassthroughSubject,就能自行建立簡單的事件匯流排。只是工具重疊,概念並沒有被取代。
Q. Pub-Sub 一定是更好的模式嗎?
不一定。如果是不需要解耦的緊密關係,觀察者更容易閱讀,也更方便除錯。
觀察者模式是直接連接,Pub-Sub 模式是透過中介者連接。差別就是這個。
兩者不是競爭關係,而是依情境選用的工具。下次設計畫面時,先想想雙方需要多了解彼此,就能很快判斷哪種模式適合。

