SwiftのCombineフレームワークでは、従来の通知受信の仕組みをより宣言的に書くことができます。
その中で登場するのが NotificationCenter.default.publisher(for:)
です。
この記事では、このAPIの意味や使い方を初心者の方にもわかりやすく解説します。
NotificationCenter.default.publisher(for:)とは?
NotificationCenter.default.publisher(for:)
は、特定の通知を CombineのPublisher(発行者) として扱えるようにするためのAPIです。
通常の NotificationCenter
では addObserver
を使って通知を受け取っていましたが、Combineでは「通知の発生を値のストリームとして扱う」という考え方に基づき、publisher(for:)
を使って通知を購読できるようにします。
特徴
- 通知の名前(Notification.Name)と、任意で通知の送信元(object)を指定できる
- 戻り値は Combine の
Publisher
型 - 通知が来たときに
Notification
オブジェクトを流してくれる - 通知の「受信」用で、通知を送るには引き続き
post
を使う
基本構文
基本構文は以下の通りです。
1 2 3 4 5 6 |
NotificationCenter.default .publisher(for: .通知名, object: nil) .sink { notification in // 通知を受け取ったときの処理 } |
引数の意味
引数 | 説明 |
---|---|
for |
監視する通知の名前(Notification.Name型) |
object |
特定の送信元に限定したい場合に指定(nilなら全て対象) |
指定できるオプション
publisher(for:)
メソッドには、通知名以外にもオプションを指定できます。
基本的な構文
1 2 3 4 5 |
NotificationCenter.default.publisher( for: Notification.Name, object: Any? = nil ) |
object パラメータ
object
パラメータを指定することで、特定のオブジェクトから送信された通知のみを受信できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class DataManager { // DataManagerのインスタンス static let shared = DataManager() } // 特定のオブジェクトからの通知のみを受信 NotificationCenter.default .publisher( for: Notification.Name("DataUpdated"), object: DataManager.shared ) .sink { notification in print("DataManagerからデータが更新されました") } .store(in: &cancellables) // 通知を送信(objectを指定) NotificationCenter.default.post( name: Notification.Name("DataUpdated"), object: DataManager.shared ) |
userInfoの活用
通知にデータを含めたい場合は、userInfo
を使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// データを含む通知の購読 NotificationCenter.default .publisher(for: Notification.Name("ScoreUpdated")) .compactMap { notification -> Int? in return notification.userInfo?["score"] as? Int } .sink { score in print("新しいスコア: \(score)") } .store(in: &cancellables) // データを含む通知の送信 NotificationCenter.default.post( name: Notification.Name("ScoreUpdated"), object: nil, userInfo: ["score": 100] ) |
.sinkとonReceiveの使い分け
.sink
は、Publisherから値を受信するための最も基本的なメソッドです。
Combineでは、publisher
が値を発行し、sink
がその値を受け取って処理を行います。
つまり、
publisher(for:)
は「通知の流れ」を作るsink
は「その流れに反応する処理」を書く場所
sink
の戻り値は AnyCancellable
で、これを保持しておくことで通知購読のキャンセル管理ができます。
ただし、SwiftUIにおいてはonReceive
を使うのが一般的です。
UIKitでは.sinkを使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class ViewController: UIViewController { private var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() // UIKitでは.sinkを使用 NotificationCenter.default .publisher(for: UIApplication.willEnterForegroundNotification) .sink { _ in print("アプリがフォアグラウンドに戻りました") } .store(in: &cancellables) } } |
SwiftUIではonReceiveを使用
1 2 3 4 5 6 7 8 9 10 11 12 13 |
struct ContentView: View { @State private var message = "待機中" var body: some View { Text(message) .onReceive( NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification) ) { _ in message = "フォアグラウンドに戻りました" } } } |
使い分けのポイント
UIKitの場合:
.sink
を使用AnyCancellable
の管理が必要- ViewControllerのライフサイクルに合わせて手動でメモリ管理
SwiftUIの場合:
onReceive
を使用- メモリ管理は自動
- Viewのライフサイクルに自動的に連動
注意点
NotificationCenter.default.publisher(for:)を使う際にはいくつか注意すべき点があります。
まず、sink
を使って通知を購読する場合は、購読を保持するために AnyCancellable
を store(in:)
で管理する必要があります。
これを忘れると、即座に購読が解除され、通知が届かなくなってしまいます。
また、通知ハンドラの中でUI更新などメインスレッドが必要な処理を行う場合は、.receive(on: RunLoop.main)
などで明示的にスレッドを切り替える必要があります。
Combineでは処理がどのスレッドで行われるかを自分で制御できるため、意識して設計することが大切です。
さらに、通知の購読数が増えすぎると、パフォーマンスへの影響やコードの可読性の低下を招く可能性があります。
特に、似たような通知を複数の場所で購読していると、どこで何をしているのか把握しづらくなるため、必要な箇所に絞って使うようにしましょう。
適材適所で使い分けることが、NotificationCenterとCombineをうまく活用する鍵です。
まとめ
NotificationCenter.default.publisher(for:)
は、通知をCombineのPublisherとして扱うための入り口です。
従来の addObserver
に比べ、柔軟で宣言的に通知を受け取れる点が魅力です。
通知の購読には .sink
や .onReceive
を使い、AnyCancellable
をしっかり管理するのがポイントです。
参考リンク:
NotificationCenter Publisher - Apple Developer Documentation
NotificationCenter - Apple Developer Documentation