NSNotificationCenterについて調べてみた

どうも僕です。

iOS開発では何かとお世話になりますNSNotificationCenterについてしっかり調べてみました。こなれてきたらこういう機会は大事です。

NSNotificationCenterってなに?

  • NSNotificationCenter Class Reference
  • Notification Centerを通じて通知を投げたり受け取ったりできる
  • アプリ内のオブジェクトにブロードキャストで通知ができる。つまり、通知したらとりあえずみんなに聞こえるということ
  • 相手を指定して通知するものではないため、クラス間の疎結合を保ったままメッセージのやり取りができる

Notification Centerを用意する

まずはNSNotificationCenterのインスタンスを取得します。

[NSNotificationCenter defaultCenter]

でアプリのデフォルトのNotification Centerが取得できます。ここに対して通知を送ったり、通知が届いているかを確認します。デフォルトのNotification Centerにはアプリの様々な通知が届きます。

もちろんalloc initして個別のインスタンスを作成してもOKです。

[NSNotificationCenter new]

通知をオブザーブして受け取る

通知を受け取るためには、Notification Centerにオブザーバを登録します。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveNotification:) name:@"XXXNotificationName" object:nil];

上の例では、nameが@"XXXNotificationName" である通知があった場合に、selfdidReceiveNotification:が呼ばれます。objectには一般的に通知の送り主であるオブジェクトを指定します。送り主が一致するものだけを受け取る事ができます。

name, objectがそれぞれnilの場合、引数に一致しない通知も受け取ることができます。アプリの通知をすべて受け取るためには、引数をnilでaddObserverします。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveNotification:) name:nil object:nil];

下のログはnamenilで受け取った通知の一部です。いろんな通知が届いていますね。どのようなNotificationが用意されているのかは、 UIApplication クラスや UIWindow クラスなどのリファレンスを見ると良いでしょう。キーボードが上がった下がったなどもNotification Center経由で取得できます。

2014-04-11 23:29:48.475 NotificationCenterTest[15537:60b] UIWindowDidBecomeVisibleNotification
2014-04-11 23:29:48.478 NotificationCenterTest[15537:60b] UIWindowDidBecomeKeyNotification
2014-04-11 23:29:48.479 NotificationCenterTest[15537:60b] UIApplicationDidFinishLaunchingNotification
2014-04-11 23:29:48.480 NotificationCenterTest[15537:60b] _UIApplicationDidRemoveDeactivationReasonNotification
2014-04-11 23:29:48.482 NotificationCenterTest[15537:60b] UIApplicationDidBecomeActiveNotification
2014-04-11 23:29:48.483 NotificationCenterTest[15537:60b] UIDeviceOrientationDidChangeNotification
2014-04-11 23:29:48.490 NotificationCenterTest[15537:60b] UIApplicationDidEndResumeAnimationNotification
2014-04-11 23:29:51.641 NotificationCenterTest[15537:60b] _UIApplicationSystemGestureStateChangedNotification
2014-04-11 23:29:51.645 NotificationCenterTest[15537:60b] _UIApplicationSystemGestureStateChangedNotification

通知はすべてのオブザーバで受信され、それぞれに実装された処理が実行されます。順番はどうやらaddObserverした順のようです。

通知を送る

Notification Centerに対して通知を送る場合は、NSNotificationクラスのオブジェクトを作成します。

NSNotification *notification = [NSNotification notificationWithName:@"XXXNotificationName" object:self];
[[NSNotificationCenter defaultCenter] postNotification:notification];

nameは衝突の可能性があるので、プレフィックスをつけるのがObjective-Cマナーです。存在するNotificationのnameを名乗っても別におこられたりはしません。

通知は基本的に同期処理で行われます。上の例で言えば、postNotificationの次の行が実行されるのはすべてのオブザーバでの処理が終わった後です。受信側で同じNotificationをpostすると無限ループします。

非同期に通知したい場合は、NSNotificationQueue クラスを利用します。

[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];

postingStyleにはNSPostASAP、NSPostWhenIdle、NSPostNow が指定できます。こちら が分かりやすいです。

通知の監視をやめる

通知はオブザーバが解放された後にも届きっぱなしになるので、適切にremoveしてやる必要があります。

[[NSNotificationCenter defaultCenter] removeObserver:self];

nameを指定して個別にremoveするメソッドも用意されています。
UIViewControllerで通知を受け取る様な場合は、画面遷移に合わせて適切にadd/removeしてあげるのが良いでしょう。

通知を受け取ってブロックを実行する

セレクタの代わりにブロックを指定して実行するメソッドも用意されています。

self.localeChangeObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"x"
                                                  object:nil
                                                   queue:[NSOperationQueue mainQueue]
                                              usingBlock:^(NSNotification *notification) {
                                                  NSLog(@"%@", notification.name);
                                              }];

queue[NSOperationQueue mainQueue]を指定した場合はメインスレッドで、nilの場合は通知の送り主と同じスレッドでブロックが実行されます。

オブザーバをremoveするためにはローカルの変数などに戻り値を保持しておく必要があるので注意です。

[[NSNotificationCenter defaultCenter] removeObserver:self.localeChangeObserver];

というわけでこれからもお世話になりますよろしくお願いします。