Swiftで遊ぼう! - 714 - Notificationでダラダラしてます。
数日前にNotificationCenterクラスに変更があったという話をしました。
Notificationの復習をしていたら、Swiftで遊ぼう! - 443 - Swiftでデザインパターン 21 Observer(オブザーバー) - Swiftで遊ぼう! on Hatenaで説明していた「ミニデモ:NotificationDemo」の解説がかなりいい加減だったことに気がついたので、過去の記事を修正します。
しかし、このデモもXcode 8βでの話です。調べているとシステムで用意されているNotificationの扱いが不十分で、ドキュメントも古い内容の説明が残っています。正式バージョンが公開される時にかなり変更が加わることが予想されます。
以前話しましたが、大きな変化として「Notification.Name」クラスの新設です。しかし、システムで用意されているNotificationはいまだに「stringly-typed」での使用になっています。
ミニデモ:NotificationDemo
Xcode 8βでプロジェクトを作成します。名前は「NotificationDemo」にします。AppDelegateとViewControllerの2つのクラスの間でメッセージのやり取りをさせます。
オブザーバー・オブジェクト:ViewController ラジオを聴くクラス
サブジェクト・オブジェクト:AppDelegate ラジオ局
最初にAppDelegateを見ていきます。AppDelegateには、アプリがバックグラウンドに入った時に実行されるメソッドが用意されています。このメソッドはNotificationではありませんが、カスタムNotification(ラジオ放送)を発生させるのに適しているため、ここにコードを実装します。
func applicationDidEnterBackground(application: UIApplication) { // ここにカスタムNotificationのポスト機能をコード }
Notificationは、新しく新設されたNotification.Nameクラスの名前を持ちます。これがラジオ番組名になります。グローバル定数としてクラス冒頭で宣言するのが望ましいんです。
static let originalNotification = Notification.Name("OriginalApplicationDidEnterBackground")
この番組を放送するメソッドが「post」です。アプリケーションがバックグラウンドに入れば放送が始まるようにします。
func applicationDidEnterBackground(_ application: UIApplication) { // 1 let ns = NotificationCenter.default() // 2 ns.post(name: AppDelegate.originalNotification, object: nil) }
- ラジオ番組を放送するNotificationCenterをシングルトン。パターンで使用します。理由は明確ですよね。複数のNotificationCenterが存在するとNotificationの処理が重複してしまう可能性が生じるからです。
- NotificationCenterにNotification.Name(ラジオ番組)を放送(post)させます。
NotificationCenterにカスタムNotificationが登録されました。
ラジオ放送ができても、リスナー(オブサーバー)が登録されていないと、メッセージは垂れ流しです。
次にオブザーバーの登録をします。オブザーバーはViewControllerなのでViewControllerを開きます。
NotificationCenterに自分自身(ViewController)を登録するのですが、インスタンス化された時に登録されるのが望ましいので、viewDidLoad()メソッドに組み込みます。super.viewDidLoad()以下にコードを加えます。
override func viewDidLoad() { super.viewDidLoad() // カスタムNotificationをNotificationCenterに登録 NotificationCenter.default().addObserver( self, selector: #selector(someAction), name: AppDelegate.originalNotification, object: nil) }
addObserver()メソッドで自分自身を登録します。レスポンスするメソッドを「selector」で指定します。そして受け取るラジオ番組はNotification.Nameクラスなので、「name」に指定します。
次は、selectorで指定した対応するメソッドを用意します。ちょっとしたデモなのでコンソールに「This is original notification message.」を表示するだけです。
func someAction(notifification: Notification) { print("This is original notification message.") }
これだけでは不十分です。何かの理由でViewControllerを消した場合、NotificationCenterに登録したViewControllerの登録がゴミとして残ってしまいます。これを避けるために必ず次のデイニシャライザを用意する必要があります。
deinit { NotificationCenter.default().removeObserver(self) }
デモプロジェクトをランすると、真っ白の画面のアプリが立ち上がります。ここでシュミレーターのメニューの「Hardware」>「Home」を選ぶと、このデモがバックグラウンドに移って、シュミレーターはホーム画面に戻ります。
すると、Xcodeのコンソールに「This is original notification message.」が表示されます。
AppDelegateからNotificationCenterにNotificationが送られてViewControllerがそれをキャッチしてアクションを起こしました。
しかし、よく考えてください。iOSフレームワークに用意されているオブジェクトには既に多彩なNotificationが用意されています。当然ですが、アプリがバックグラウンドに移った時にNotificationもポストされています。このデモのように自分でカスタムNotificationを作ると機能が重複してしまうのでシステムで用意されているNotificationも受け取ってみましょう。
実はAppDelegateクラスのapplicationDidEnterBackground()メソッドが実行される時にシステムはNotificationを自動的に発信しています。それは「UIApplicationDidEnterBackgroundNotification」というString型*1です。これはシステムで予約されている定数です。
これを確認するためにviewDidLoad()メソッドを次のように拡張させます。
override func viewDidLoad() { super.viewDidLoad() // カスタムNotificationをNotificationCenterに登録 NotificationCenter.default().addObserver( self, selector: #selector(someAction), name: AppDelegate.originalNotification, object: nil) // システムで用意されているNotificationは「String」型を引数として受け取れます。 NotificationCenter.default().addObserver( self, selector: #selector(anotherAction), name: "UIApplicationDidEnterBackgroundNotification", object: nil) }
そしてもう一つメソッドを用意します。
func anotherAction(notifification: Notification) { print("This is system notification message.") }
ランをして白紙のViewControllerをHomeに切り替えると、コンソロールに次のメッセージが並びます。
This is original notification message.
This is system notification message.
Notificationを自作する場合、システムで用意されているかどうか確認する必要があります。もし用意されているようならシステムの予約後を使わないと非効率的になりますね。
これでミニプロジェクトは終わります。
*1:まだwrapper typesは用意されていないようです。