SwiftでiOSアプリを開発していると、異なるクラス間でデータのやり取りや状態の変更を通知したい場面がよくあります。
そんな時に便利なのがNotificationCenterです。
本記事では、NotificationCenterの基本概念から実際の使い方まで、初心者にもわかりやすく解説します。
NotificationCenterとは?
NotificationCenterは、アプリ内の異なるオブジェクト間で通知を送受信するための仕組みを提供するクラスです。
基本的な概念
NotificationCenterは「放送局」のようなものと考えることができます:
- 送信者(Publisher):通知を送る側
- 受信者(Observer):通知を受け取る側
- NotificationCenter:通知を仲介する中央管理システム
1 2 3 4 |
送信者 → NotificationCenter → 受信者A → 受信者B → 受信者C |
主な特徴
- 疎結合:送信者と受信者が直接的な関係を持たない
- 一対多通信:一つの通知を複数のオブジェクトが受信可能
- 非同期通信:通知の送信と受信は独立している
重要なポイント
NotificationCenterはシングルトンパターンを採用しており、アプリ全体で一つのインスタンスを共有します。
1 2 3 4 5 6 7 8 |
// デフォルトのインスタンスを取得 let notificationCenter = NotificationCenter.default // 常に同じインスタンスが返される let center1 = NotificationCenter.default let center2 = NotificationCenter.default print(center1 === center2) // true |
独自のNotificationCenterインスタンスを作成することも可能ですが、通常はNotificationCenter.default
を使用します。
NotificationCenterでよく使うプロパティやメソッド
NotificationCenterで頻繁に使用されるプロパティとメソッドを紹介します。
主要なプロパティ
default(静的プロパティ)
アプリ全体で共有されるデフォルトのNotificationCenterインスタンスです。
1 2 |
let center = NotificationCenter.default |
主要なメソッド
1. publisher(通知の受信)
通知をCombineのPublisherとして扱うメソッドです。
現在最も推奨される方法です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import Combine class DataManager { private var cancellables = Set<AnyCancellable>() func setupNotificationListener() { // 基本的な使用方法 NotificationCenter.default.publisher(for: Notification.Name("DataUpdate")) .sink { notification in print("通知を受信: \(notification)") } .store(in: &cancellables) // 高度な処理チェーン NotificationCenter.default.publisher(for: Notification.Name("UserAction")) .compactMap { $0.userInfo?["userId"] as? String } .filter { !$0.isEmpty } .debounce(for: .milliseconds(300), scheduler: RunLoop.main) .receive(on: DispatchQueue.main) .sink { userId in print("ユーザーID: \(userId) のアクションを処理") } .store(in: &cancellables) } } |
2. post(通知の送信)
通知を送信します。
1 2 3 4 5 6 7 8 9 10 |
// 基本的な送信 NotificationCenter.default.post(name: Notification.Name("MyNotification"), object: nil) // データ付きで送信 NotificationCenter.default.post( name: Notification.Name("DataUpdate"), object: self, userInfo: ["key": "value"] ) |
3. addObserver(従来の方法)
通知を受信するためのオブザーバーを登録します。
現在でも使用可能ですが、publisherメソッドの使用が推奨されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// セレクターベース NotificationCenter.default.addObserver( self, selector: #selector(handleNotification(_:)), name: Notification.Name("MyNotification"), object: nil ) // クロージャベース let observer = NotificationCenter.default.addObserver( forName: Notification.Name("MyNotification"), object: nil, queue: .main ) { notification in print("通知を受信: \(notification)") } |
4. removeObserver(オブザーバーの削除)
登録したオブザーバーを削除します。
publisherメソッドを使用する場合は、AnyCancellableが自動的に管理するため、通常は不要です。
1 2 3 4 5 6 7 8 9 |
// 特定の通知のオブザーバーを削除 NotificationCenter.default.removeObserver( self, name: Notification.Name("MyNotification"), object: nil ) // オブジェクトの全てのオブザーバーを削除 NotificationCenter.default.removeObserver(self) |
どういう時によく使われる?
NotificationCenterは以下のような場面でよく使用されます。
1. アプリの状態変更の通知
アプリがバックグラウンドに移行したり、フォアグラウンドに復帰した時の処理に使用されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import Combine import UIKit class AppStateManager { private var cancellables = Set<AnyCancellable>() init() { setupAppStateNotifications() } private func setupAppStateNotifications() { // バックグラウンド移行の通知 NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification) .sink { [weak self] _ in self?.handleAppDidEnterBackground() } .store(in: &cancellables) // フォアグラウンド復帰の通知 NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification) .sink { [weak self] _ in self?.handleAppWillEnterForeground() } .store(in: &cancellables) } private func handleAppDidEnterBackground() { // データを保存する処理 print("アプリがバックグラウンドに移行") } private func handleAppWillEnterForeground() { // データを更新する処理 print("アプリがフォアグラウンドに復帰") } } |
2. データの更新通知
データベースやAPIからデータが更新された時に、複数の画面に変更を通知する場合に使用されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
import Combine class ProductManager { private var products: [Product] = [] private var cancellables = Set<AnyCancellable>() func addProduct(_ product: Product) { products.append(product) // 商品追加を通知 NotificationCenter.default.post( name: .dataDidUpdate, object: self, userInfo: [ "action": "add", "product": product, "count": products.count ] ) } func updateProduct(_ product: Product) { // 商品を更新 if let index = products.firstIndex(where: { $0.id == product.id }) { products[index] = product } // 商品更新を通知 NotificationCenter.default.post( name: .dataDidUpdate, object: self, userInfo: ["action": "update", "product": product] ) } } // 複数の画面で受信可能 class ProductListViewController: UIViewController { private var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() setupNotifications() } private func setupNotifications() { NotificationCenter.default.publisher(for: .dataDidUpdate) .compactMap { $0.userInfo?["action"] as? String } .receive(on: DispatchQueue.main) .sink { [weak self] action in switch action { case "add", "update": self?.refreshProductList() default: break } } .store(in: &cancellables) } private func refreshProductList() { print("商品リストを更新") // テーブルビューのリロードなど } } |
3. 設定変更の通知
アプリの設定が変更された時に、関連する画面に変更を通知する場合に使用されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
enum AppTheme: String { case light = "light" case dark = "dark" case system = "system" } class SettingsManager { func updateTheme(_ theme: AppTheme) { UserDefaults.standard.set(theme.rawValue, forKey: "app_theme") // テーマ変更を通知 NotificationCenter.default.post( name: .themeDidChange, object: nil, userInfo: ["theme": theme] ) } } class BaseViewController: UIViewController { private var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() setupThemeNotifications() } private func setupThemeNotifications() { NotificationCenter.default.publisher(for: .themeDidChange) .compactMap { $0.userInfo?["theme"] as? AppTheme } .receive(on: DispatchQueue.main) .sink { [weak self] theme in self?.applyTheme(theme) } .store(in: &cancellables) } private func applyTheme(_ theme: AppTheme) { switch theme { case .light: overrideUserInterfaceStyle = .light case .dark: overrideUserInterfaceStyle = .dark case .system: overrideUserInterfaceStyle = .unspecified } } } |
4. キーボードの表示・非表示
キーボードの状態変化に応じてUIを調整する場合に使用されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import Combine import UIKit class ChatViewController: UIViewController { @IBOutlet weak var messageInputBottom: NSLayoutConstraint! private var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() setupKeyboardNotifications() } private func setupKeyboardNotifications() { // キーボード表示通知 NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification) .compactMap { notification in notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect } .receive(on: DispatchQueue.main) .sink { [weak self] keyboardFrame in self?.messageInputBottom.constant = keyboardFrame.height self?.view.layoutIfNeeded() } .store(in: &cancellables) // キーボード非表示通知 NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification) .receive(on: DispatchQueue.main) .sink { [weak self] _ in self?.messageInputBottom.constant = 0 self?.view.layoutIfNeeded() } .store(in: &cancellables) } } |
使用上の注意とベストプラクティス
1. publisherメソッドを優先する
現在のSwift開発では、Combineフレームワークを活用したpublisher
メソッドの使用が推奨されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// ✅ 推奨:publisherメソッドを使用 NotificationCenter.default.publisher(for: .dataDidUpdate) .receive(on: DispatchQueue.main) .sink { [weak self] notification in self?.handleNotification(notification) } .store(in: &cancellables) // ⚠️ 非推奨:addObserverメソッド NotificationCenter.default.addObserver( self, selector: #selector(handleNotification), name: .dataDidUpdate, object: nil ) |
2. メモリ管理
publisher
メソッドを使用する場合、AnyCancellable
が自動的にメモリ管理を行います。
従来のaddObserver
を使用する場合は、手動でremoveObserver
を呼ぶ必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// ✅ publisherメソッド:自動メモリ管理 class ModernViewController: UIViewController { private var cancellables = Set<AnyCancellable>() // cancellablesが自動的にリソースを解放 } // ⚠️ addObserver:手動メモリ管理が必要 class TraditionalViewController: UIViewController { deinit { NotificationCenter.default.removeObserver(self) } } |
3. 通知名の型安全な管理
通知名はNotification.Name
の拡張として定数で定義しましょう。
1 2 3 4 5 6 7 8 |
// ✅ 推奨:型安全な定数定義 extension Notification.Name { static let dataDidUpdate = Notification.Name("dataDidUpdate") } // ❌ 非推奨:文字列の直接使用 NotificationCenter.default.post(name: Notification.Name("dataDidUpdate"), object: nil) |
4. スレッドセーフティ
UI更新を行う場合は、メインスレッドで実行されることを確認しましょう。
1 2 3 4 5 6 7 |
NotificationCenter.default.publisher(for: .dataDidUpdate) .receive(on: DispatchQueue.main) // メインスレッドで受信 .sink { [weak self] _ in self?.updateUI() // UI更新 } .store(in: &cancellables) |
5. パフォーマンスへの配慮
頻繁に発生する通知にはdebounce
オペレータを使用して処理を最適化しましょう。
1 2 3 4 5 6 7 |
NotificationCenter.default.publisher(for: .frequentUpdate) .debounce(for: .milliseconds(300), scheduler: RunLoop.main) .sink { _ in // 300ms以内の連続した通知をまとめて処理 } .store(in: &cancellables) |
まとめ
NotificationCenterの要点は下記のとおりです。
基本概念
- シングルトンパターン:
NotificationCenter.default
を使用 - 疎結合通信:送信者と受信者が直接関係を持たない
- 一対多通信:複数のオブジェクトが同じ通知を受信可能
推奨される使用方法
- publisherメソッド:Combineを活用した現代的なアプローチ
- 型安全な通知名:
Notification.Name
の拡張を使用 - 自動メモリ管理:
AnyCancellable
による自動リソース解放
主な使用場面
- アプリ状態の変更通知
- データ更新の通知
- ユーザー認証状態の管理
- 設定変更の通知
- キーボード状態の監視
NotificationCenterは、異なるオブジェクト間の疎結合な通信を実現する強力な仕組みです。