Objective-Cで、参照先のオブジェクトから参照元に通知を送る方法。」を読んてで思ったんですが、若干冗長な気がする。

プロトコルを使う

プロトコルを使うんだったら、最初からそのプロトコルに適合するオブジェクトしかdelegateになれないようにしちゃった方がいいと思う。こんな感じで。

@interface Foo : NSObject {
  id<SampleDelegate> delegate;
}
@property (assign) id<SampleDelegate> delegate;
@end

こうしちゃえば、そもそもSampleDelegateに適合しないオブジェクトはdelegateになれないので、conformsToProtocolのチェックは必要なくなる。UIKitでも(例えばUITableViewのdelegateとか)大体そうなってますね。あと、ObjCはレシーバがnilの場合はメッセージ送信を単に無視するので、respondToSelectorとか送ってもYESが返らないのでこの場合はnitチェックもしなくていいと思う。だから、notifyObjectChangedの定義はこれで十分。

- (void)notifyObjectChanged {
  if ([self.delegate respondsToSelector:@selector(objectChanged:)]) {
    [self.delegate objectChanged:self];
  }
}

非形式プロトコル(カテゴリ)を使う

プロトコルがoptionalなメソッドしか規定してなくて、あるメソッドを定義したデリゲートがある場合はそれを呼ぶけど、そうでない場合は無視するかデフォルトの動作をする、っていうような場合は、そもそもプロトコルで規定しないでカテゴリで実現しちゃう手もある。

@interface NSObject (SampleDelegate)
- (void)objectChanged:(id)object;
@end

@interface Foo : NSObject {
  id delegate;
}
@property (assign) id delegate;
@end

ちなみにnotifyObjectChangedの実装は同じでいい。このタイプの例はNSURLConnectionとか。

こっちの利点はプロトコルの宣言がいらないので、既存のクラスや外部のライブラリのクラスのインスタンスでもdelegateにできること。それが何であるかはどうだっていいんだ、ただ送ったメッセージに答えてくれる何かでありさえすれば、みたいなときにはこの方法でもいい。

欠点は、プロトコルと違ってコンパイル時にdelegateが期待してるオブジェクトかどうかを解決できないこと。元記事の例みたいにoptionalなメソッドしか規定してない場合だと、プロトコルに適合してるからと言って期待してるオブジェクトかどうかははっきりしないので大差ないんだけど、仮にそうであってもプロトコル宣言を強制することによってデリゲートになり得るクラスを限定するってこともあるので、この辺はケースバイケース。

Key-Value Observingを使う

あと、「あるオブジェクトのあるプロパティが変更されたことを知りたい」っていう用途に限って言えば、Key-Value Observingを使う手もある。

@implementation Bar

- (void)test {
  Foo *f = [[Foo alloc] init];
  [f addObserver:self
      forKeyPath:@"hoge"
         options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld)
         context:NULL];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    // do something
}

@end

こういう風にしておくと、fのhogeプロパティの値が変更されたときに、BarのインスタンスのobserveValueForKeyPath:ofObject:change:context:メソッドが呼ばれる。その際、keyPathには@"hoge"が、objectにはfが、changeには{ old: /* 変更前の値 */, new: /* 変更後の値 */ }というDictionaryが、contextにはNULLが渡ってくる。

これの利点は、「Fooクラス側には全く手を加えなくていい」「あらゆるオブジェクトに対して、一貫したインターフェースで同じように設定できる」ということ。Cocoaのクラスだろうが外部のライブラリのクラスだろうが自分で作ったクラスだろうが同じように「あるプロパティの値が変更されたら教えてねー」っていう設定ができるし、参照先のオブジェクトが参照元のオブジェクトのことを気にする必要が無くなって関係性が緩くできる。Barの仕様を変更したくなってもFooをいじる必要はない。あと、delegateの場合と違って通知を受けとるオブジェクトが複数設定できるので、アプリケーションのあちこちが同時多発的に状態変化するみたいなことができる。

欠点は、「f.hogeに代入するか、[f setValue:obj forKey:@"hoge"]が呼ばれたときだけしか通知されない」ということ。なのでFooクラスの内部でインスタンス変数を直接弄って内部状態が変わったりしたときには通知されない。あとは、あらゆるオブジェクトに対して使えるので便利なんだけど、どのオブジェクトからの通知も必ずobserveValueForKeyPath:ofObject:change:context:を呼ぶので、沢山通知を設定するとobserveValueForKeyPath:ofObject:change:context:の中身が大分カオスなことになるので、ご利用は計画的に。

他には

NSNotificationを使う方法もあるけど、多用すると処理がどこでどうなるかわかりづらくなるので割と慎重に使った方がいいかも。まぁこれはKey-Value Observingでもそうなんだけど、NSNotificationの方がより汎用性が高いのでよりこんがらがり易くてよくハマる。