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"))の1行で終わりです。
ログイン画面はホーム画面の存在すら知る必要がありません。
オブザーバー vs Pub-Subを表で比較
言葉だけでは似て見えるので、違いを表にまとめました。
| 項目 | オブザーバーパターン | Pub-Subパターン |
|---|---|---|
| 仲介役 | なし(直接接続) | あり(イベントバス) |
| 結合度 | 対象が購読者を知っている | 互いに知らない(疎結合) |
| 関係 | 主に1:N | N:Nも可能 |
| iOSの例 | KVO, @Published |
NotificationCenter、イベントバス |
| 適した場面 | 特定オブジェクトの状態監視 | 離れたモジュール間の通信 |
面白いことに、NotificationCenterは実際にはPub-Subに近い存在です。
名前は「Notification」ですが、発行者と購読者はNotificationCenterというバスを通じてのみ出会います。互いを直接参照しません。
そのため「オブザーバーパターン=NotificationCenter」と覚えると少しずれます。概念的にはPub-Subに近いものです。
では、いつ何を使うべき?
実務で使っている判断基準を紹介します。
1つのオブジェクトの状態変化を近い範囲で監視するなら、オブザーバー系が便利です。Combineの@PublishedやSwiftUIの@Observableがまさに適しています。
一方、離れたモジュール間やアプリ全体で起きるログイン、決済完了、ネットワーク切断などの大きなイベントを伝えるなら、イベントバスのほうがずっとすっきりします。
ただし、イベントバスも万能ではありません。
多用すると「このイベントはいったいどこから来たの?」と流れを追いにくくなります。疎結合を得る代わりに、見通しを少し失うわけです。
そこで私は、画面内のロジックにはオブザーバー(Combine)を使い、画面やモジュールをまたぐ大きなイベントだけをイベントバスで扱っています。
Q. Combineを使えばイベントバスは不要ですか?
いいえ。CombineのPassthroughSubjectだけで、簡単なイベントバスを自作できます。道具が重なるだけで、概念が置き換わるわけではありません。
Q. Pub-Subは常に優れたパターンですか?
そうとは限りません。疎結合にする必要のない近い関係なら、オブザーバーのほうが読みやすくデバッグもしやすいです。
オブザーバーパターンは直接接続、Pub-Subパターンは仲介役を介した接続。違いはこれだけです。
両者は競合するものではなく、状況に応じて選ぶ道具です。次に画面を1つ作るときは、両者が互いをどれだけ知る必要があるかを考えてみてください。どのパターンが合うか、すぐ見えてくるはずです。

