NotificationCenter は、送信者と受信者の直接的な接続なしに、アプリケーションコンポーネント間で通知を送受信するためのiOSのシステムメカニズムです。Observerパターンに基づき、NotificationCenterはオブジェクトがイベントに購読し、非同期に応答することを可能にします。Apple Documentation(2025)によると、NSNotificationCenterはpost(name:object:)による同期的な通知送信と、NotificationQueueによる遅延送信の両方をサポートしています。通知センターは単一プロセス内で動作し、アプリケーションの境界を越えません。
重要ポイント
NotificationCenter(NSNotificationCenter)は、オブジェクト間の疎結合通信を実装するための組み込みiOSメカニズムです。Observerパターンにより、1つのオブジェクト(送信者)が、直接的な参照なしに、複数の他のオブジェクト(オブザーバー)にイベントを通知できます。NotificationCenterは3つのエンティティで動作します:Notification.Name(通知識別子)、Notification(データを含むコンテナ)、NotificationCenter(ディスパッチャー)。各アプリケーションには共有のdefault centerがあります。
Notification.Name は通知タイプを識別する構造体です。extension Name: Notification.Name(“MyNotification”) で作成します。Notificationは、name、object(送信者)、userInfo(データを含む辞書)を含むオブジェクトです。システム通知は定数として宣言されます:UIApplication.didBecomeActiveNotification、UIResponder.keyboardWillShowNotification。カスタム通知は、名前の衝突を避けるためにextensionでグループ化する必要があります。名前は逆ドメイン形式にすべきです。
// カスタム通知の定義
extension Notification.Name {
static let dataDidUpdate =
Notification.Name("com.app.dataDidUpdate")
static let userLoggedOut =
Notification.Name("com.app.userLoggedOut")
}
// データ付き通知の送信
let userInfo: [String: Any] = [
"userId": 123,
"timestamp": Date()
]
NotificationCenter.default.post(
name: .dataDidUpdate,
object: nil,
userInfo: userInfo
)
オブザーバーはaddObserver(_:selector:name:object:)メソッドを介して通知に購読します。Selector は通知を受信したときに呼び出されるメソッドです。objectパラメータは特定の送信者からの通知をフィルタリングできます。objectがnilの場合、オブザーバーは任意の送信者からの指定された名前のすべての通知を受信します。iOS 9以降、addObserverはblock-based APIでは手動削除を必要としませんが、selector-basedは引き続きremoveObserverが必要です。
// 通知への購読(selector-based)
NotificationCenter.default.addObserver(
self,
selector: #selector(handleDataUpdate),
name: .dataDidUpdate,
object: nil
)
@objc func handleDataUpdate(_ notification: Notification) {
guard let userId = notification.userInfo?["userId"] as? Int else { return }
updateUI(for: userId)
}
// 通知への購読(block-based、iOS 9+)
var observer: NSObjectProtocol?
observer = NotificationCenter.default.addObserver(
forName: .dataDidUpdate,
object: nil,
queue: .main
) { [weak self] notification in
guard let self else { return }
self.handleNotification(notification)
}
NotificationCenterはマッピングテーブル(名前 → オブザーバーの集合)を保存します。送信者がpost(name:object:)を呼び出すと、通知センターは同期的にその名前に購読しているすべてのオブザーバーを巡回し、それらのセレクタまたはブロックを呼び出します。重要な特徴:postはすべてのハンドラが完了するまで現在のスレッドをブロックします。ハンドラが重い処理を実行する場合、送信者が遅延します。NotificationQueueは通知の配信を遅延させることでこの問題を解決します。
post(name:object:userInfo:)メソッドは即座にすべてのオブザーバーに通知を送信します。呼び出しは同期的です — post後のコードはすべてのハンドラが完了した後にのみ実行されます。オブザーバーの呼び出し順序は保証されておらず、実行ごとに変わる可能性があります。順次処理には、coalescing付きのNotificationQueueを使用してください。同じ通知のハンドラ内でpostを呼び出さないでください — 無限再帰を引き起こします。
NotificationQueue は非同期配信のために通知をキューに追加します。coalescing(同一通知の統合)と配信キューの選択(asap、idle、modal)をサポートします。Coalescingは、最後の値のみで通知すればよい頻繁なイベント(ダウンロード進捗)に役立ちます。NotificationQueueは実行ループを使用して発火するため、アクティブな実行ループがあるスレッドでのみ動作します。
// NotificationQueueによる遅延送信
let notification = Notification(
name: .dataDidUpdate,
object: self,
userInfo: ["progress": 0.5]
)
// Coalescing:複数の通知が1つに統合される
NotificationQueue.default.enqueue(
notification,
postingStyle: .whenIdle,
coalesceMask: .onName,
forModes: [.common]
)
// DispatchQueueによる非同期配信
DispatchQueue.main.async {
NotificationCenter.default.post(name: .dataDidUpdate, object: nil)
}
iOSはオブジェクト間の通信に3つの主要なメカニズムを提供します:NotificationCenter、Delegate、KVO(Key-Value Observing)。それぞれが通知の問題を解決しますが、結合度、パフォーマンス、型安全性において異なるトレードオフがあります。メカニズムの選択は、1対1または1対多の関係とデータ転送の必要性に依存します。
| 特性 | NotificationCenter | Delegate | KVO |
|---|---|---|---|
| 結合度 | 弱い(通知名) | 強い(プロトコル) | 中程度(キー) |
| 関係 | 1対多 | 1対1 | 1対多 |
| 型安全性 | 低い(userInfoがDictionary) | 高い(プロトコルメソッド) | 中程度(Any?) |
| パフォーマンス | 中程度(テーブル走査) | 高い(直接呼び出し) | 低い(NSObject) |
| 非同期性 | 同期(postがブロック) | 送信者のスレッドで同期 | 変更時に同期 |
NotificationCenterは、複数の独立したコンポーネントが応答する必要があるイベントに最適です。例:アプリ設定の変更、ユーザーログアウト、プッシュ通知のバックグラウンド受信。NotificationCenterは疎結合モジュール(機能Aが機能Bについて知る必要がない)にも適しています。欠点は型安全性の欠如です:userInfoのキーは文字列であり、enumではありません。
Delegate は明確なコントラクト(tableView.delegate)を持つ1対1の関係に適しています。Delegateはより高速で型安全です。KVO はモデルの特定のプロパティ(isLoading、progress)の変更を監視する場合に適しています。KVOはNSObjectからの継承が必要で、デバッグが困難になる可能性があります(マジックストリングキー)。最新のSwiftでは、Combineとasyncシーケンスが3つのアプローチすべてを置き換えます。
addObserverメソッドは2つの購読方法をサポートします:selector-based(従来型)とblock-based(クロージャ使用)。Selector-based は@objc互換性と手動のオブザーバー削除が必要です。Block-based(iOS 9+)はキャプチャリストを使用でき、強い参照なしでブロックを使用する場合、OSによって自動的に管理されます。Block-basedはキューもサポートします — オブザーバーは指定されたキューで通知を受信します。
セレクタを介した従来の購読方法。ハンドラメソッドは@objcでマークされ、オプションのNotificationを受け取る必要があります。利点:レガシーObjective-Cを含む任意のクラスで使用可能。欠点:セレクタの型安全性の欠如、セレクタ名のタイポのリスク、deinitでの必須のremoveObserver。オブジェクトより先にオブザーバーが削除された場合、ハンドラは呼び出されません。
Block-based APIは、通知受信時に実行されるクロージャを受け入れます。queueパラメータ はブロックが実行されるキューを決定します — UI更新のためのメインキュー、またはデータ処理のためのバックグラウンドキュー。戻り値のNSObjectProtocolはオブザーバーの削除に使用されます:NotificationCenter.default.removeObserver(observer)。Block-basedは最新のSwiftで推奨されます。
protocol NotificationToken {
func dispose()
}
extension NotificationCenter {
func observe(
name: NSNotification.Name,
object: Any? = nil,
queue: OperationQueue? = .main,
using block: @escaping (Notification) -> Void
) -> NotificationToken {
let observer = addObserver(forName: name, object: object,
queue: queue, using: block)
return NotificationTokenWrapper(observer: observer, center: self)
}
}
// 自動削除と共に使用
class ViewModel {
private var tokens: [NotificationToken] = []
func startObserving() {
let token = NotificationCenter.default.observe(
name: .dataDidUpdate,
queue: .main
) { [weak self] notification in
self?.handleUpdate(notification)
}
tokens.append(token)
}
deinit {
tokens.forEach { $0.dispose() }
}
}
メモリリーク はNotificationCenterを使用する際の主要な問題の1つです。オブザーバーが解放前に削除されていない場合、通知送信時にセンターは既に解放されたオブジェクトのメソッドを呼び出そうとし、EXC_BAD_ACCESSが発生します。iOS 9以降、block-based addObserverは弱参照を使用しますが、selector-basedは引き続き手動のremoveObserverが必要です。ベストプラクティス:deinitでオブザーバーを削除します。
Selector-based:常にdeinitでNotificationCenter.default.removeObserver(self)を呼び出します。オブザーバーが複数の通知に購読している場合、すべてを一度に(パラメータなしで)、または名前で特定のものを削除できます。Block-based:addObserverから返されたトークンを使用してremoveObserverで削除します。iOS 9+のblock-basedではリークは発生しませんが、パフォーマンスのために削除を推奨します:解放されたオブザーバーはpost中に巡回されなくなります。
class SafeObserver {
private var observers: [NSObjectProtocol] = []
func addSubscriptions() {
let token1 = NotificationCenter.default.addObserver(
forName: .dataDidUpdate, object: nil,
queue: .main) { [weak self] _ in
self?.refreshData()
}
let token2 = NotificationCenter.default.addObserver(
forName: .userLoggedOut, object: nil,
queue: .main) { [weak self] _ in
self?.logout()
}
observers.append(contentsOf: [token1, token2])
}
deinit {
observers.forEach { NotificationCenter.default.removeObserver($0) }
}
private func refreshData() { }
private func logout() { }
}
トークンパターンはオブザーバー管理を自動化します。購読時に、トークンオブジェクト(NSObjectProtocol)が返され、解放時に自動的にオブザーバーを削除します。NotificationTokenWrapper はNotificationCenterとオブザーバートークンへの弱参照を保存し、deinitでremoveObserverを呼び出します。これにより、NotificationCenterはAnyCancellableが購読ライフサイクルを管理するCombineアプローチに近づきます。
スレッドセーフ:NotificationCenterは、postが任意のスレッドから呼び出せ、すべてのオブザーバーがpostが呼び出された同じスレッドで通知を受信することを保証します。これはマルチスレッドアプリケーションにとって重要です:バックグラウンドスレッドから通知が送信された場合、ハンドラもバックグラウンドスレッドで実行されます。UI更新の場合は、DispatchQueue.main.asyncを介してメインキューに処理をディスパッチします。
NotificationCenterは異なるスレッドからのpostおよびaddObserver呼び出しに対してスレッドセーフです。内部同期 はロックを使用するため、複数のスレッドからの頻繁なpostはコンテンションを引き起こす可能性があります。高負荷シナリオ(1000ファイルのダウンロード進捗)では、別の通知キューまたはCombineパブリッシャーを使用してください。postingStyle .nowのNotificationQueueは直接のpostと同等です。
NotificationCenterはNotificationCenter.default.publisher(for:object:)を介してCombineパブリッシャーをサポートします。Publisher は各通知をCombineイベントに変換し、map、filter、debounce、throttleで変換できます。これにより同期配信の問題が解決されます:Combineは指定されたSchedulerで非同期に通知を処理します。NotificationCenter.publisherはレガシーメカニズムと最新のリアクティブプログラミングの間の橋渡しです。
import Combine
class ReactiveViewModel {
private var cancellables = Set<AnyCancellable>()
func setupCombineSubscription() {
NotificationCenter.default
.publisher(for: .dataDidUpdate)
.receive(on: DispatchQueue.main)
.compactMap { $0.userInfo?["progress"] as? Float }
.debounce(for: .seconds(0.3), scheduler: RunLoop.main)
.sink { [weak self] progress in
self?.progressLabel.text = "\(Int(progress * 100))%"
}
.store(in: &cancellables)
}
}
よくある質問
はい、NotificationCenterは任意のスレッドからのpostおよびaddObserver呼び出しに対してスレッドセーフです。ただし、ハンドラはpostが呼び出された同じスレッドで実行されます。UI更新の場合は、block-based addObserverでqueue: .mainを使用するか、ハンドラ内でDispatchQueue.main.asyncを使用してください。receive(on:)を使用したCombineパブリッシャーもスレッド問題を解決します。
Selector-based:オブザーバー解放後の通知送信時にEXC_BAD_ACCESSクラッシュ。Block-based(iOS 9+):弱参照によりリークはありませんが、通知センターは明示的なremoveObserverまでブロックをメモリに保持します。常にdeinitでオブザーバーを削除するか、自動管理のためにトークンパターンを使用することをお勧めします。
NotificationCenter は無関係なコンポーネント間の任意のイベントのブロードキャストメカニズムです。KVO は特定のオブジェクトの特定のプロパティの変更を監視します。KVOはNSObjectの継承が必要で、setterを介したプロパティ変更時に自動的に通知します。NotificationCenterは明示的にpostが呼び出された場合にのみ通知します。モデル監視にはKVOまたはCombineが推奨されます。
アプリケーションプロセスごとに1つのdefault center。NotificationCenter()で追加のセンターを作成できますが、実際には共有のdefaultが使用されます。各センターは独立して動作します — 一方でのpostは他方のオブザーバーに配信されません。モジュール分離には、逆ドメイン通知名を使用して個別のName名前空間を使用してください。
部分的に。CombineはNotificationCenter.Publisherを提供し、NotificationCenterをリアクティブストリームでラップします。Combineは同期問題(receive(on:)経由)を解決し、変換オペレーターと自動購読管理(AnyCancellable)を追加します。ただし、NotificationCenterはiOSシステム通知(UIApplication、UIKeyboard)およびレガシーコードのために残ります。Combineは拡張であり、置き換えではありません。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。