KVO (Key-Value Observing)

Mac OS Xのプログラミングに詳しい方はとっくにご存じだと思うが、この「プロパティの変更通知機能」はCocoaフレームワークとして既に実装されているのだ。

それが Key-Value Observing 略して KVOである。
Key-Value Observing Programming Guide: Introduction to Key-Value Observing Programming Guide

KVOは最初からCocoa Foundationフレームワークに組込まれており、使うのは簡単だ。

KVOを扱うための条件
    • NSKeyValueObservingカテゴリに準拠したクラス、オブジェクト
    • NSKeyValueObserverRegistrationカテゴリに準拠したクラス、オブジェクト
    • KVC(Key-Value Coding)に準拠したクラス、オブジェクトとそのコーディング

これらの条件を満たしていればKVOを使えるのだが、NSKeyValueObservingとNSKeyValueObserverRegistrationカテゴリはNSObjectに対して定義されており、つまりは殆どのクラスが対象となる。

NSKeyValueObserving.h (部分)
@interface NSObject(NSKeyValueObserving)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
@end

@interface NSObject(NSKeyValueObserverRegistration)
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end

NSKeyValueObserving Protocol Reference

例えば、辞書(NSDictionary)の変更を検知するためのKVOのコーディングは以下のようになるだろう。

TestCocoaKVONotification_Dictionary.m (テスト用クラス)
static char TestCocoaKVONotification_Dictionary; //どのコンテキストで変更が発生するかを判断するために使うらしい。正直あまり意味は無い。
- (void)testCocoaKVONotification_Dictionary
{
    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"foo", @"key", nil];
    [dict addObserver:self forKeyPath:@"key" options:NSKeyValueObservingOptionNew context:&TestCocoaKVONotification_Dictionary];
     
    [dict setObject:@"bar" forKey:@"key"];
    
    [dict removeObserver:self forKeyPath:@"key" context:&TestCocoaKVONotification_Dictionary];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ( context == &TestCocoaKVONotification_Dictionary)
    {
        NSLog(@"%s: %@ changed %@ dictionary %@", __func__, object, keyPath, change);
    }
}

コードは、辞書インスタンスのaddObserver:forKeyPath:options:contextメソッドで監視者(オブザーバ)に現在のクラス、キーパスに辞書のプロパティである"key"を登録して監視を開始、その後辞書がsetObject:forKeyメソッドで値を変更するとKVOが働き、observeValueForKeyPath:ofObject:change:contextメソッドがコールバックされるという訳だ。

このように便利なKVOだが、今ひとつ使いにくい部分もある。 その辺は次回にでも。