廃墟

本ブログは更新を終了しました。 技術的な記事のみ、有用性を鑑みて残しておきます。

viewDidUnloadが使えなくなった今、ViewControllerでどうやってNSNotificationをobserveするか

表題の通りですが、ViewControllerでNSNotificationを受け取る事は非常に典型的なパターンです。
ここでは2通りの想定パターンについて簡単に紹介します。 (以下のコードはARCを利用している事を前提としています)

画面が表示されてないときはNotificationを受け取らなくて良い場合

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeHoge:) name:AKNHogeDidChangeNotification object:nil];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

画面が表示されてないときにもNotificationを受け取りたい場合

こっちが本題です。

viewDidUnoadがあった頃は、viewDidUnloadとdeallocでやれば良かったのですが、そういうわけにはいかなくなりました。

また、アプリケーションの状態遷移によっては以下の様なケースがあるようで、これらに対処しなきゃなりません。

  • ビューが最初にロードされる時にviewDidLoadが呼ばれる
  • 他の画面に遷移
  • メモリ警告発生(didReceiveMemoryWarningが呼ばれる)
  • 元の画面に遷移
  • viewDidLoadがまた呼ばれる

参考記事でも言及されているように、何も考慮せずにviewDidLoadでaddObserverしてdeallocでremoveObserverするだけだとNotificationを二回受け取る事になってしまうので、都合が悪いです。

なので、didReceiveMemoryWarningでremoveObserverしても良いんですけど、今後のiOSアップデートでちょっとでもイベントループが変化すると破綻するような感じで、あんまりやりたくない方針。

また、init系メソッドでaddObserverするという手もありますが、ビューのないビューコントローラでNotificationを受け取ったらクラッシュしそうだし、それを防ぐ制御コードを書くのも何だか馬鹿らしい。

などと考えた結果、以下の様にしちゃえば良いという事に気づきました。

- (void)viewDidLoad
{
    [self addNotificationObserver];
}
- (void)dealloc
{
    [self removeNotificationObserver];
}

- (void)addNotificationObserver
{
    // 念のため先に削除する。
    [self removeNotificationObserver];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeHoge:) name:AKNHogeDidChangeNotification object:nil];
}
- (void)removeNotificationObserver
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

流石に、これが期待通りに動かなくなるようなイベントループの変更はこないでしょう。たぶん。。

大いに参考にした記事

もとい、元ネタです。 iOS5まではこの方法で良かったんだよね。*1

*1:僕が本格的にコード書き始めたのはiOS6からだから、詳しくは知らないんですけどね。