読者です 読者をやめる 読者になる 読者になる

Objective-C で通知の解放忘れとかもろもろを解決するデザインパターン

20%ルール Objective-C

こんにちわ、 20%ルールの時間を使って30分で書きます。

Objective-C の Notification って便利ですよね。 しかし、色々な問題点を抱えています。

下のサンプルコードを例に説明します。

// 通知を受け取るオブザーバーを登録
- (instancetype)init {
    self = [super init];
    if (self) {

        NSNotificationCenter *c = NSNotificationCenter.defaultCenter;
        
        [c addObserver:self
              selector:@selector(safe_didChangeHogehoge:)
                  name:kHogeNotificator_safe_DidChangeHogeHogeNotification
                object:nil];
        
        [c addObserver:self
              selector:@selector(unsafe_didFail:)
                  name:kHogeNotificator_unsafe_HogeHogeDidFailNotification
                object:nil];
    }
    return self;
}

// オブザーバーを登録解除
- (void)dealloc {
    [NSNotificationCenter.defaultCenter removeObserver:self];
}

#pragma mark - 通知が来たら実行されるメソッド達

- (void)safe_didChangeHogehoge:(NSNotification *)note {
  // なんかする    
}

- (void)unsafe_didFail:(NSNotification *)note {
  // なんかする
}

Notification を使うときに発生する問題点

Notification では以下の様な問題があります。

  • オブザーバーの登録が面倒
    • NSNotificationCenter.defaultCenter って長い
  • dealloc でオブザーバーの登録解除を忘れてクラッシュ
  • セレクタの名前を変えた時に addObserverセレクタ名を変更し忘れてクラッシュ
  • NSNotification オブジェクトの userInfo の中身がなんなのか分からない

dealloc 忘れてないかとか頑張ってチェックするしかないというのでは辛いです。

なので

Notification を Delegate に変換するパターンを使ってもろもろの問題を一気に解決します。

このパターンは臭いコードを1箇所に押し込めることによって色々な所に臭いコードが散らばらないようにするという方針で設計されています。

どうやるの?

  • Notification を Delegate に変換する Dispatcher クラスを作る
  • Dispatcher クラスで addObserverremoveObserver を管理する
  • サンプルのコードを作ったのでそちらを見て頂くほうが早いですが Dispatcher はクラスはこんな感じです
    • Notification を Delegate に変換しています
    • userInfo の中身をばらして、 Delegate の引数にしています
@implementation HogeNotificationDispatcher

- (instancetype)initWithNotificator:(HogeNotificator *)notificatorOrNil {
    self = [super init];
    if (self) {
        _notificator = notificatorOrNil;
        
        NSNotificationCenter *c = NSNotificationCenter.defaultCenter;
        
        [c addObserver:self
              selector:@selector(safe_didChangeHogehoge:)
                  name:kHogeNotificator_safe_DidChangeHogeHogeNotification
                object:_notificator];
        
        [c addObserver:self
              selector:@selector(unsafe_didFail:)
                  name:kHogeNotificator_unsafe_HogeHogeDidFailNotification
                object:_notificator];

    }
    return self;
}

- (void)dealloc {
    [NSNotificationCenter.defaultCenter removeObserver:self];
}

#pragma mark - Notification

- (void)safe_didChangeHogehoge:(NSNotification *)note {
    
    NSString *hogehoge = note.userInfo[kHogeNotificatorHogeHogeUserInfoKey];
    
    [self.delegate safe_dispatcher:self didChangeHogeHoge:hogehoge];
}

- (void)unsafe_didFail:(NSNotification *)note {
    
    NSError *error = note.userInfo[kHogeNotificatorErrorUserInfoKey];
    
    [self.delegate unsafe_dispatcher:self didFail:error];
}

@end

利点

  • Dispatcher クラスの deallocremoveObserver されていれば他の場所で考える必要がなくなる
  • ViewController はテストしにくいけど Dispatcher クラス単体ならテスト簡単 (OCMock とか使う)
  • どんな通知が来るか分からなくても Delegate が実装されてないと警告が出るので思考停止してても実装できる
  • userInfo は何が入ってるかよくわからないけど、 Delegate のメソッドの引数になってれば何が入ってるか一目瞭然
note.userInfo[kHogeHogeUserInfoKey]: // 何が入ってるかわからん
- (void)dispatcher:(Dispatcher *)dispatcher didChangeHogehoge:(NSString *)hogehoge {

  // NSString の hogehgoe ってやつが入ってることが分かる

}

欠点

  • コード増える
    • 最初コードを増えるだけで通知を受け取る場所が増えればコードの総量は減ります

サンプルコード

NotificationDispatcherSample

ってなかんじです。

13:00 から書き始めて 13:29 に書き終わり!!!!