Doc-Based App + Cocoaバインディングで
ドキュメントのDirtyフラグを立てるには?


ドキュメントベースのアプリでCocoaバインディングを使用した場合、モデルデータがControllerオブジェクトに完全に握られているので、データの更新の有無をNSDocumentオブジェクトでどうやって認識することができるのかネットなどで検索して調べてもよくわからないでいたのですが、ふとNSDocumentがKVO(Key-Value Ovserving)でController内に保持しているモデルデータの変化を監視すればよいのではと気がつきました。

作成したサンプルプログラムはヒレガス本(MacOS X Cocoaプログラミング)の「RaiseMan」サンプルをCocoaバインディングを使って実装したものです。



ちなみにモデルデータの持ち方は「Doc-Based App + Cocoaバインディングでのもう一つのモデルデータの持ち方(っていうかこっちが本命?)」で示したうちの下図の方式を採用しています。



キーとなる処理は以下の通りです。

1.NSDocumentをControllerのObserverとして登録する

NSDocument(のサブクラス)のwindowControllerDidLoadNib:メソッド内で、Controllerに対してNSKeyValueObservingプロトコルのaddObserver:forKeyPath:options:context:メソッドで監視したい値に対して自分をObserverとして登録します。
なお、登録した値に変化が生じた時、後述するobserveValueForKeyPath:ofObject:change:context:メソッドを実装することでこれが呼ばれます。
具体的なコードは以下の通りです。

- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
    [super windowControllerDidLoadNib:aController];
    // Add any code here that needs to be executed once the windowController has loaded the document's window.
    if ( _parrPersons != nil ) {
        // ファイルから読み込んだデータをControllerに設定
        [_outPersonCtrl setContent:_parrPersons];
        [_parrPersons release];
        _parrPersons = nil;
    }

    //***** Controller内の値の変化を監視するように設定 *****
//    [_outPersonCtrl addObserver:self forKeyPath:@"content" options:NSKeyValueObservingOptionNew context:nil];    =>NSArrayControllerでは変更は通知されない
//    [_outPersonCtrl addObserver:self forKeyPath:@"arrangedObjects" options:NSKeyValueObservingOptionNew context:nil]; =>Arrayの追加削除は通知されるが個別項目の変更が通知されない
    [_outPersonCtrl addObserver:self forKeyPath:@"arrangedObjects.personName" options:NSKeyValueObservingOptionNew context:nil];
    [_outPersonCtrl addObserver:self forKeyPath:@"arrangedObjects.expectedRaise" options:NSKeyValueObservingOptionNew context:nil];
}

コードの中にコメントアウトしてある行がありますが、これは試行錯誤した結果を意図的に残したものです。
最初のコメントアウト行はControllerのcontentを監視しようとしたのですが、このサンプルはNSArrayControllerを使用しているため、いくらデータを変更しても全く変更通知が来ませんでした。
次のコメントアウト行はNSArrayControllerのarrangedObjects監視しようとしたのです。この場合、Arrayの要素の追加/削除に関しては通知が来るのですが、要素内の個別項目の値を変更した場合は通知が来ませんでした。
以上から監視したいすべての個別項目に対して自らをObserverとして登録しなければならないことがわかりました。

2.変更通知メソッドの実装

値の変更通知を受け取るためにNSKeyValueObservingプロトコルのobserveValueForKeyPath:ofObject:change:context:メソッドを実装します。
ここでの処理内容はNSDocumentのupdateChangeCount:メソッドを呼び出し、ドキュメントデータが変更されたことを通知することです。
具体的なコードは以下の通りです。

//
// データの変更通知
//
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog( @"Value changed.(%@)\n", keyPath );
    [self updateChangeCount:NSChangeDone];
}



サンプルプログラムのダウンロード(64KB)

【作成・確認環境】
Mac OS X v10.4.6
Xcode v2.3


一覧に戻る