iPhoneアプリ作成までの道のり11

11. KVO

Objective-CにはNSObjectレベルでObserverパターンが実装されており、ある条件をクリアするだけで、まるで魔法のように状態変更通知を処理することができるようになる。KVC(key-valueコーディング), KVO(key-value Observing)については、詳解Objective-Cで詳しく解説されている。特にKVCの中身の仕組みついて詳しく書かれているわけだが、使うだけなら簡単なので、実際にコードを見てもらう方が理解が早いと思われる。

//  KVOを試してみる簡単なサンプル

#import <Foundation/Foundation.h>

// Subject
@interface Model : NSObject
{
    int _x, _y;
}
@property(assign, nonatomic) int x;
@property(assign, nonatomic) int y;
@end

@implementation Model
@synthesize x = _x;
@synthesize y = _y;
@end

// Observer
@interface View : NSObject
{
    Model* _model;
}
@property(retain, nonatomic) Model* model;
@end

@implementation View
@synthesize model = _model;
-(void)dealloc {
    self.model = nil;
    [super dealloc];
}

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

    if ([@"x" isEqualToString:keyPath]) {
        NSLog(@"xの値が変更されました。x=%d, y=%d", _model.x, _model.y);
    }
    else if ([@"y" isEqualToString:keyPath]) {
        NSLog(@"yの値が変更されました。x=%d, y=%d", _model.x, _model.y);
    }
}

@end

// テスト用メイン関数
int main(int argc, const char * argv[])
{
    @autoreleasepool {
        Model* model = [[[Model alloc] init] autorelease];
        View* view = [[[View alloc] init] autorelease];
        view.model = model;
        
        // SubjectにObserverを登録する
        [model addObserver:view 
                forKeyPath:@"x" 
                   options:NSKeyValueObservingOptionNew 
                   context:nil];
        
        [model addObserver:view 
                forKeyPath:@"y" 
                   options:NSKeyValueObservingOptionNew 
                   context:nil];        
        
        // Subjectに対するxとyの変更はObserverに通知される
        model.x = 1;
        model.y = 100;
        model.x = 999;
        model.y = -999;
        
        // Observerを解除する
        [model removeObserver:view forKeyPath:@"x"];
        [model removeObserver:view forKeyPath:@"y"];
    }
    
    return 0;
}

Observerパターンを使ったことのない人からすると、まるで魔法のように見えるのではないだろうか。実際、強力な設計方法だ。ただし万能ではない。Subjectの状態が変わるたびにObserverへの通知が行くということで、それなりのオーバーヘッドを覚悟しなければならない。本当にSubjectの状態変更を逐一Observerが監視しなければならない場合にのみ使用すべきである。そして、6章で述べたように、この方法はViewがModelに直接的に依存しているということを忘れてはならない。

KVOを使用するためには、対象のプロパティがKVCに準拠していなければならないという条件があるのだが、単値(配列ではない)のプロパティであれば、その条件はほぼないに等しいほどだ。物凄く雑に言ってしまえば、以下のようにすれば良い。

・Subject側のクラスをNSObjectから派生させること
・forKeyPathに指定する文字列と監視対象のプロパティ名を同じ名前に合わせて、デフォルトのgetter, setter名を使用する

監視対象が配列のような複数の値を示すものである場合、KVCの規約に従ったメソッドの実装が必要になる。ここでは説明を割愛させていただく。

forKeyPathでアクセス監視したい対象の属性が、あるクラスがoという名前で保持するオブジェクトのxという属性である場合は、forKeyPathに指定する名前を "o.x"とする。この時、oとxはKVCに準拠している必要がある。このように、.(ドット)で名前を繋ぐことによって、階層構造のプロパティを監視することも可能になる。

より詳しい理解が必要だと感じたら、詳解Objective-Cを参照されたし。