SwiftでマスターするNotificationCenterの10のステップ

SwiftのNotificationCenterを使ったプログラミングの基本と応用を学ぶためのイメージSwift
この記事は約27分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

アプリ開発を行う上で、特定のイベントが発生した時に通知を受け取る仕組みは欠かせない要素の一つです。

Swiftを使ったiOSアプリ開発では、この通知の仕組みを実現するために「NotificationCenter」が提供されています。

この記事を読めば、SwiftでNotificationCenterを使った通知の管理がスムーズにできるようになります。

具体的には、基本的な使い方から応用例、注意点、カスタマイズ方法まで、10のステップでNotificationCenterの全てを習得することができます。

初心者の方でも安心して学べるよう、分かりやすい説明と具体的なサンプルコードを交えて詳しく解説していきます。

●SwiftとNotificationCenterとは

○Swiftの基本

SwiftはAppleが開発したプログラミング言語で、iOSやmacOSなどのApple製品のアプリを開発するための言語として普及しています。

CやObjective-Cに比べ、文法がシンプルで直感的なため、初心者でも比較的取り組みやすいのが特徴です。

○NotificationCenterの概要

NotificationCenterは、Swiftにおける通知の仕組みを提供するクラスです。

異なるクラスやオブジェクト間でイベントを通知したり、データをやり取りする際に用いられます。

具体的には、特定のイベントが発生した際に他のオブジェクトに通知することで、アプリ全体でのデータの連携や動作の調整を行うことができます。

NotificationCenterの仕組みは、「発行-購読」モデルに基づいています。

これは、あるオブジェクトがイベントを「発行」すると、そのイベントを「購読」している他のオブジェクトが通知を受け取る、という流れになります。

例えば、ユーザーがアプリのボタンをタップした際に、その情報を別の画面に伝えるようなケースでは、ボタンがタップされたイベントをNotificationCenterで「発行」し、受け取りたい画面側でそのイベントを「購読」することで、イベントが発生した際の処理を実行することができます。

●NotificationCenterの使い方

SwiftにおけるNotificationCenterの使い方は、通知を送るオブジェクトと受け取るオブジェクトの間に位置します。

通知の送り方と受け取り方、オブザーバの登録・削除など、いくつかの基本的なステップが必要です。

ここでは、その手順を詳しく解説していきます。

○サンプルコード1:基本的な通知の送り方と受け取り方

NotificationCenterを使って通知を送り、受け取る基本的な方法を紹介します。

import Foundation

// 1. 通知の名前を定義
let sampleNotificationName = Notification.Name("sampleNotification")

// 2. 通知を受け取るオブジェクトの設定
NotificationCenter.default.addObserver(self, selector: #selector(receiveNotification(_:)), name: sampleNotificationName, object: nil)

// 3. 通知を送る
NotificationCenter.default.post(name: sampleNotificationName, object: nil)

@objc func receiveNotification(_ notification: Notification) {
    print("通知を受け取りました!")
}

このコードでは、まずNotification.Nameで通知の名前を定義しています。

次に、addObserverメソッドで通知を受け取るオブジェクトを設定し、最後にpostメソッドで通知を送信しています。

このコードを実行すると、”通知を受け取りました!”というメッセージが表示されることが確認できます。

○サンプルコード2:オブザーバの登録と削除

アプリ開発を進める中で、特定のタイミングで通知の受け取りを停止したい場面が出てくるかと思います。

その場合は、オブザーバを削除することで、通知の受け取りを停止することができます。

import Foundation

// 通知の名前を定義
let sampleNotificationName = Notification.Name("sampleNotification")

// 通知を受け取るオブジェクトの設定
NotificationCenter.default.addObserver(self, selector: #selector(receiveNotification(_:)), name: sampleNotificationName, object: nil)

// 通知のオブザーバを削除
NotificationCenter.default.removeObserver(self, name: sampleNotificationName, object: nil)

@objc func receiveNotification(_ notification: Notification) {
    print("通知を受け取りましたが、既にオブザーバは削除されています。")
}

このコードでは、removeObserverメソッドを使って、特定の通知のオブザーバを削除しています。

そのため、通知を受け取ることはできません。

実際にコードを実行しても、”通知を受け取りましたが、既にオブザーバは削除されています。”というメッセージは表示されません。

○サンプルコード3:セレクタを使った通知のハンドリング

SwiftでのNotificationCenterのハンドリングには、セレクタを使用します。

セレクタは、特定のメソッドを識別するための機構です。

NotificationCenterにおける通知の受け取り方をカスタマイズする際に、このセレクタが役立ちます。

import Foundation

// 通知の名前を定義
let sampleNotificationName = Notification.Name("sampleNotification")

class SampleClass {
    init() {
        // 通知を受け取るオブジェクトの設定
        NotificationCenter.default.addObserver(self, selector: #selector(handleNotification(_:)), name: sampleNotificationName, object: nil)
    }

    @objc func handleNotification(_ notification: Notification) {
        if let data = notification.userInfo as? [String: String] {
            for (key, value) in data {
                print("\(key) : \(value)")
            }
        }
    }
}

// インスタンスを作成
let instance = SampleClass()

// 通知を送る
NotificationCenter.default.post(name: sampleNotificationName, object: nil, userInfo: ["key1": "value1", "key2": "value2"])

このサンプルコードでは、セレクタ#selector(handleNotification(_:))を用いて、handleNotificationメソッドが通知を受け取る際のハンドラとして指定しています。

このメソッド内で、userInfoプロパティを通じて通知と一緒に送られてきたデータを取得し、その内容を表示しています。

コードを実行すると、キーと値のペア”key1 : value1″と”key2 : value2″が表示されることが確認できます。

これにより、通知と共に追加の情報やデータを送ることができるのがわかります。

○サンプルコード4:通知に情報を添付して送る方法

NotificationCenterを利用して、通知に追加の情報やデータを添付する方法を紹介します。

この機能は、アプリの様々な部分から通知を送る際に、その通知がどのような内容や目的で送られてきたのかを明確にするのに役立ちます。

import Foundation

// 通知の名前を定義
let detailedNotificationName = Notification.Name("detailedNotification")

class DetailedClass {
    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(handleDetailedNotification(_:)), name: detailedNotificationName, object: nil)
    }

    @objc func handleDetailedNotification(_ notification: Notification) {
        if let message = notification.userInfo?["message"] as? String {
            print("受け取った通知の内容: \(message)")
        }
    }
}

// インスタンスを作成
let detailedInstance = DetailedClass()

// 通知を送る際に情報を添付
NotificationCenter.default.post(name: detailedNotificationName, object: nil, userInfo: ["message": "これは詳細な通知の例です"])

このサンプルコードでは、userInfoプロパティを使用して、通知に”message”というキーで文字列を添付しています。

受け取り側のhandleDetailedNotificationメソッドでこの情報を取り出し、内容を表示しています。

コードを実行すると、”受け取った通知の内容: これは詳細な通知の例です”というメッセージが表示されます。

このように、NotificationCenterを使用することで、通知に様々なデータや情報を添付して送信することが可能です。

●NotificationCenterの応用例

NotificationCenterはアプリの様々な部分で活用できる強力なツールです。特定のイベントや状態変化を通知することで、異なるコンポーネント間でのデータ共有や同期を効率的に行うことができます。ここでは、その応用例をいくつか紹介していきます。

○サンプルコード5:アプリ内でのデータの同期

アプリ内で複数の部分が同じデータに依存している場合、そのデータが変更されたときにすべての部分を更新する必要があります。NotificationCenterを利用することで、このようなデータの同期を効率的に実現することができます。

import Foundation

let dataUpdatedNotificationName = Notification.Name("dataUpdated")

class DataProvider {
    var data: String = "" {
        didSet {
            NotificationCenter.default.post(name: dataUpdatedNotificationName, object: nil)
        }
    }
}

class DataObserver {
    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(dataDidUpdate), name: dataUpdatedNotificationName, object: nil)
    }

    @objc func dataDidUpdate() {
        print("データが更新されました。")
    }
}

let provider = DataProvider()
let observer = DataObserver()

provider.data = "新しいデータ"

このコードでは、DataProviderクラスがデータを保持し、そのデータが変更されるたびに通知を送信します。一方、DataObserverクラスはその通知を受け取り、データの更新を検知します。

上記のコードを実行すると、”データが更新されました。”というメッセージが表示されることを確認できます。

○サンプルコード6:ユーザーインタフェースの更新

ユーザーインタフェース(UI)の更新もNotificationCenterの応用例の一つです。

例えば、ユーザーのアクションに応じてUIの一部を更新する必要がある場合、NotificationCenterを利用することで効率的にその処理を行うことができます。

import UIKit

let uiUpdatedNotificationName = Notification.Name("uiUpdated")

class ViewController: UIViewController {

    let label = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()

        NotificationCenter.default.addObserver(self, selector: #selector(updateUI), name: uiUpdatedNotificationName, object: nil)
    }

    @objc func updateUI() {
        label.text = "UIが更新されました。"
    }

    func userDidPerformAction() {
        NotificationCenter.default.post(name: uiUpdatedNotificationName, object: nil)
    }
}

このサンプルコードでは、ViewController内でuserDidPerformActionメソッドが呼び出されると、UIが更新されるという仮定の下、UILabelのテキストが更新されるようになっています。

上記のコードの中でuserDidPerformActionメソッドが呼び出されると、”UIが更新されました。”というテキストがラベルに表示されることを想定しています。

このように、NotificationCenterを利用することで、アプリの異なる部分間での状態の変化やUIの更新を効率的に管理することができます。

○サンプルコード7:外部デバイスとの通信

Swiftを利用したiOSアプリケーション開発では、BluetoothやWi-Fiなどを介して外部デバイスとの通信が求められる場面が多々あります。

NotificationCenterは、これらの外部デバイスからのデータ受信やデバイスの状態変更を検知して、アプリ内の異なるコンポーネントに通知する役割を果たします。

例えば、Bluetoothを用いた外部デバイスからのデータ受信を想定した次のサンプルコードを考えてみましょう。

import CoreBluetooth

let deviceDataReceivedNotificationName = Notification.Name("deviceDataReceived")

class BluetoothManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
    var centralManager: CBCentralManager!
    var peripheral: CBPeripheral!

    override init() {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: nil)
    }

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {
            // BluetoothがONの場合、デバイスのスキャンを開始
            centralManager.scanForPeripherals(withServices: nil, options: nil)
        }
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
        self.peripheral = peripheral
        self.peripheral.delegate = self
        centralManager.stopScan()
        centralManager.connect(self.peripheral, options: nil)
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if let data = characteristic.value {
            // 受信したデータをNotificationCenterで通知
            NotificationCenter.default.post(name: deviceDataReceivedNotificationName, object: data)
        }
    }
}

class DataHandler {
    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(handleReceivedData), name: deviceDataReceivedNotificationName, object: nil)
    }

    @objc func handleReceivedData(notification: Notification) {
        if let data = notification.object as? Data {
            print("外部デバイスからのデータを受信しました:\(data)")
        }
    }
}

let bluetoothManager = BluetoothManager()
let dataHandler = DataHandler()

このサンプルコードでは、BluetoothManagerクラスが外部デバイスとのBluetooth通信を管理しています。

デバイスからデータを受信した際、そのデータをNotificationCenterを使って他のコンポーネントに通知しています。

一方、DataHandlerクラスはその通知を受け取り、受信したデータを処理しています。

このコードを利用すると、外部デバイスからのデータを受信するたびに”外部デバイスからのデータを受信しました:”というメッセージとともに、受信したデータがコンソールに表示されます。

○サンプルコード8:マルチスレッド環境での使用

iOSアプリケーションでは、スムーズなユーザーエクスペリエンスを提供するために、複数の処理を同時に実行するマルチスレッド環境が必要とされることがよくあります。

NotificationCenterは、異なるスレッド間でデータの共有や通知の受け渡しを行う場面で非常に役立ちます。

考えられるシナリオとして、バックグラウンドスレッドでデータの処理を行い、その結果をメインスレッドでUIに反映する場合を想定してみましょう。

import Foundation

let dataProcessedNotificationName = Notification.Name("dataProcessed")

class BackgroundProcessor {
    func processData() {
        DispatchQueue.global().async {
            // 何らかの時間のかかるデータ処理
            sleep(2)

            // 処理が完了したら、その結果をNotificationCenterで通知
            NotificationCenter.default.post(name: dataProcessedNotificationName, object: "処理完了!")
        }
    }
}

class UIUpdater {
    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(updateUI), name: dataProcessedNotificationName, object: nil)
    }

    @objc func updateUI(notification: Notification) {
        if let message = notification.object as? String {
            DispatchQueue.main.async {
                print("UI更新: \(message)")
            }
        }
    }
}

let processor = BackgroundProcessor()
let uiUpdater = UIUpdater()
processor.processData()

このコードでは、BackgroundProcessorクラスがバックグラウンドスレッドでデータの処理を行い、その処理が完了したらNotificationCenterを使って結果を通知しています。

一方、UIUpdaterクラスはその通知を受け取り、メインスレッドでUIを更新しています。

上記のコードを実行すると、約2秒後に”UI更新: 処理完了!”というメッセージがコンソールに表示されることを確認できます。

このように、NotificationCenterを利用することで、異なるスレッド間でのデータのやり取りを簡単かつ効率的に行うことができます。

●注意点と対処法

SwiftのNotificationCenterを使用する際には、いくつかの注意点が存在します。

これらの問題を未然に防ぐための対処法も一緒に紹介します。

○スレッドセーフとは

スレッドセーフとは、マルチスレッド環境であるアプリケーションにおいて、複数のスレッドが同時にアクセスしてもプログラムの動作に問題が生じないことを指します。

NotificationCenterは内部でスレッドセーフではないため、複数のスレッドから同時にアクセスする場合、不具合や予期せぬ動作が発生する可能性があります。

□スレッドセーフを確保する方法

NotificationCenterをスレッドセーフに使用するための基本的な手段として、DispatchQueueを利用した同期処理を行う方法が考えられます。

let notificationCenterQueue = DispatchQueue(label: "com.example.notificationCenterQueue")

notificationCenterQueue.sync {
    NotificationCenter.default.addObserver( ... )
    // その他のNotificationCenter関連の処理
}

このコードの中で、特定のDispatchQueueを作成して、そのキュー上でNotificationCenterのメソッドを同期的に実行しています。

これにより、NotificationCenterへのアクセスがシリアルになり、複数のスレッドからの同時アクセスを防止することができます。

○メモリリークのリスク

NotificationCenterを使用する際、オブザーバを適切に解放しないと、メモリリークが発生する可能性があります。

メモリリークとは、不要となったオブジェクトがメモリ上から解放されずに残り続けることで、長時間の使用や頻繁な通知の追加と削除を繰り返すとアプリケーションのパフォーマンスに影響が出ることが考えられます。

□メモリリークを防ぐテクニック

NotificationCenterからオブザーバを適切に削除することで、メモリリークを防ぐことができます。

具体的には、オブザーバを追加した際には、適切なタイミングでremoveObserverメソッドを使用して削除します。

class SomeClass {
    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(handleNotification), name: .someNotificationName, object: nil)
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    @objc func handleNotification(_ notification: Notification) {
        // 通知のハンドリング処理
    }
}

このコードでは、SomeClassのインスタンスが破棄される際、deinit内でremoveObserverを呼び出して、オブザーバをNotificationCenterから削除しています。

これにより、オブジェクトが不要になったときに適切にオブザーバを解放することができ、メモリリークを防ぐことができます。

○通知のオーバーヘッド

大量の通知を短時間に送信すると、アプリケーションのパフォーマンスに影響を与える可能性があります。

特に大量のデータや頻繁に状態が変わるような場合、通知のオーバーヘッドが気になる場面が出てくるかもしれません。

□パフォーマンスを最適化する方法

通知のオーバーヘッドを軽減するための一つの方法は、不要な通知の送信を避けることです。

例えば、同じデータが短時間に複数回更新される場合、その都度通知を送信するのではなく、一定の間隔でまとめて通知を送信するように変更することで、オーバーヘッドを軽減することができます。

また、通知の内容をできるだけシンプルに保つこともオーバーヘッドの軽減に役立ちます。

大量のデータや複雑なオブジェクトを通知の内容として送信するのではなく、必要最低限の情報のみを含めるように工夫することで、通知の処理負荷を低く保つことができます。

●カスタマイズ方法

SwiftでのNotificationCenterの使用において、標準の機能だけでは物足りない、もしくは特定のニーズを満たすために、カスタマイズする方法があります。

ここでは、カスタム通知の作成や通知のグルーピング、さらにNotificationCenterの拡張方法について詳しく解説します。

○サンプルコード9:カスタム通知の作成

通知内容をカスタマイズして送受信するための方法を紹介します。

まずは、カスタム通知のデータ構造を定義します。

struct CustomNotificationData {
    var message: String
    var timestamp: Date
}

次に、このデータ構造を利用して、通知を送信するコードを紹介します。

let data = CustomNotificationData(message: "新しい通知があります", timestamp: Date())
NotificationCenter.default.post(name: NSNotification.Name("CustomNotification"), object: nil, userInfo: ["data": data])

通知を受信する側では、次のようにデータを取り出して利用できます。

NotificationCenter.default.addObserver(forName: NSNotification.Name("CustomNotification"), object: nil, queue: nil) { notification in
    if let data = notification.userInfo?["data"] as? CustomNotificationData {
        print("メッセージ: \(data.message), タイムスタンプ: \(data.timestamp)")
    }
}

このコードを実行すると、カスタムデータを含む通知が送信され、受信側でそのデータを取り出して利用することができます。

○サンプルコード10:通知のグルーピング

複数の通知をグルーピングして一括で処理する方法を紹介します。

下記のコードは、特定のカテゴリに基づいて通知をグルーピングする例です。

enum NotificationCategory: String {
    case uiUpdate = "UI_UPDATE"
    case dataSync = "DATA_SYNC"
}

NotificationCenter.default.post(name: NSNotification.Name("GroupedNotification"), object: nil, userInfo: ["category": NotificationCategory.uiUpdate.rawValue, "message": "UIの更新が必要です"])
NotificationCenter.default.post(name: NSNotification.Name("GroupedNotification"), object: nil, userInfo: ["category": NotificationCategory.dataSync.rawValue, "message": "データの同期が完了しました"])

受信側では、通知のカテゴリに応じて異なる処理を行うことができます。

NotificationCenter.default.addObserver(forName: NSNotification.Name("GroupedNotification"), object: nil, queue: nil) { notification in
    if let category = notification.userInfo?["category"] as? String, let message = notification.userInfo?["message"] as? String {
        switch category {
        case NotificationCategory.uiUpdate.rawValue:
            print("UI更新のための通知: \(message)")
        case NotificationCategory.dataSync.rawValue:
            print("データ同期のための通知: \(message)")
        default:
            break
        }
    }
}

このコードを実行すると、カテゴリごとに異なるメッセージが表示されることを確認できます。

○NotificationCenterの拡張

Swiftの拡張機能を使用して、NotificationCenterに追加の機能を提供することも可能です。

下記のサンプルコードでは、特定の名前の通知のみを受信するためのメソッドをNotificationCenterに追加しています。

extension NotificationCenter {
    func addObserver(forName name: NSNotification.Name, using block: @escaping (Notification) -> Void) {
        self.addObserver(forName: name, object: nil, queue: nil, using: block)
    }
}

この拡張メソッドを使用すると、特定の通知名でのオブザーバの登録が簡単になります。

NotificationCenter.default.addObserver(forName: .init("SpecificNotification")) { notification in
    print("特定の通知を受信しました")
}

このように、SwiftのNotificationCenterは非常に柔軟性が高く、様々なカスタマイズが可能です。

上記のカスタマイズ例を参考に、自分のニーズに合わせた最適な通知の仕組みを構築してみてください。

まとめ

SwiftにおけるNotificationCenterは、アプリケーションのさまざまな部分で情報をやりとりするための強力なツールです。

この記事を通して、基本的な使い方から高度なカスタマイズ方法まで、NotificationCenterの魅力的な機能を学ぶことができたかと思います。

NotificationCenterの利用は、アプリケーションのコンポーネント間での通信を効率的に行うことを可能にします。

特に、UIの更新やデータの同期など、異なる部分が連携するシチュエーションでの有効性が高まります。

また、注意点やオーバーヘッドの考慮、さらにはスレッドセーフな使用方法なども紹介しました。

これらは、NotificationCenterを安全かつ効果的に使用するための知識として非常に重要です。

カスタマイズの部分では、通知の内容を自由に定義したり、通知のグルーピング、さらにはNotificationCenter自体の拡張方法についても触れました。

これにより、より柔軟な通知の仕組みを自ら作成することが可能となります。

Swiftでのアプリ開発を進める中で、NotificationCenterはその中心的な役割を果たすツールの一つです。

この記事の内容をしっかりと理解し、日々の開発に活かしてください。

NotificationCenterをマスターすることで、より洗練され、効率的なアプリ開発が実現します。