[Objective-C] KeyValueObserve(キー値監視)メモ

  • 165
    Like
  • 2
    Comment
More than 1 year has passed since last update.

Cocoaフレームワークには、KVOと呼ばれるオブザーバの機能を標準で擁しています。
基本的な考え方は、KVO用に作られたメソッドを経由してプロパティを操作することで、適切にその変化をオブザーバに通知する、という仕組みです。

KVOに準拠するためにはプロパティのアクセサメソッドを適切に設定しないとなりませんが、通常のプロパティを使っている場合は特に問題なく準拠できていることになります。

KVOの登録

とあるプロパティの値の変化を監視する場合。

// HogeClassのインスタンスを生成。`fuga`というプロパティを持っている想定
HogeClass *hoge = [[HogeClass alloc] init];

[hoge addObserver:self forKeyPath:@"fuga" options:NSKeyValueObservingOptionNew context:nil];

監視対象オブジェクトのaddObserver:forKeyPath:options:context:メソッドにメッセージを送信し、監視元(オブザーバ)として自分自身を登録しています。
この登録を行ったクラス側では、以下のメソッドを実装する必要があります。(値が変化した際に呼び出されるメソッドです)

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    // 変化した値がなにかを判別
    if ([keyPath isEqual:@"fuga"]) {
        // 処理したいキーだった場合は処理
    }
}

ちなみに渡される引数は以下の通りです。

引数
keyPath 変化したキー fuga
object 監視対象オブジェクト hoge
change 変更された値 fugaの値と変化した状態の情報
context 任意のオブジェクト nil

contextについて

contextに渡す値はオブザーバに伝えたい任意の値です。
Appleのガイドラインから引用すると、

オブジェクトをオブザーバとして登録するときに、コンテキストポインタを提供することもできま
す。コンテキストポインタは、observeValueForKeyPath:ofObject:change:context:が呼び出さ
れたときに、オブザーバに渡されます。コンテキストポインタとしては、C言語のポインタおよびオ
ブジェクト参照を指定できます。コンテキストポインタは、監視の対象となる変化を識別する一意の
識別子として、あるいは、オブザーバに何らかのデータを渡すために使用できます。

このcontextを使用する際、ARC環境下では適切にキャストしないとコンパイルエラーになって使用できません。
具体的にはbridgeキャストを用いて以下のようにする必要があります。

self.context = @"hoge";
[hogeInstance addObserver:self forKeyPath:@"fuga" options:NSKeyValueObservingOptionNew context:(__bridge void *)self.context];
// 通知を受け取ったとき
NSString *hoge = (__bridge NSString *)context;

ちなみに__bridgeキャストについては以下の記事が分かりやすいです。
[iOS5] ARC : Autorelease, キャスト, 環境設定

KVOの削除

当然、登録した監視は外すことが出来ます。

[observedObject removeObserver:observer forKeyPath:@"fuga"];

observedObjectは監視対象オブジェクト、observerは監視元オブジェクトです。
(例ではobservedObjectHogeClassオブジェクト)

手動変更通知

監視対象となるプロパティによっては、すべての通知が行われると処理が多くなる場合があります。
その場合は、該当プロパティを手動通知に切り替えてまとめて通知したり、といったことが可能になっています。

手動通知を行う場合はautomaticallyNotifiesObserversForKey:メソッドを実行し、その中で自動通知か手動通知かを振り分けます。

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey
{
    BOOL automatic = NO;
    if ([theKey isEqualToString:@"fuga"]) {
        automatic = NO;
    }
    else {
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    }

    return automatic;
}

さらに、willChangeValueForKey:didChangeValueForKey:メソッドも実装する必要があります。

手動通知を実装するアクセサメソッドの例

- (void)setFuga:(NSString *)theFuga
{
    [self willChangeValueForKey:@"fuga"];
    _fuga = theFuga;
    [self didChangeValueForKey:@"fuga"];
}