KVO(Key-Value Observing)を試してみる


Cocoaバインディングを構成するキー・テクノロジーの1つにKey-Value Observing(KVO)というのがあります。
KVOというのは、M-V-Cモデルを構成するオブジェクト間で各オブジェクトが持つプロパティの値が変更されたときに、そのプロパティを参照しているオブジェクト(Observer)に対して値が変更されたという通知が自動的に送られ、たとえばビューがObserverであればその表示内容が更新されるといった機能です。

KVOの全容についてはmkinoさんのHMDTサイトにあるAppleドキュメントの和訳記事を参照してください。(mkinoさんのHMDTにはほんとにお世話になっていますm(__)m)

今回「KVO(Key-Value Observing)を試してみる」と題して、Cocoaバインディングのサンプルをいじってみるのですが、タイトルの内容から想像されるほど「KVOを完全マスター」みたいなたいしたことをやるのではなく、

CocoaバインディングでつながったM-V-C各オブジェクトの中の、末端のモデルオブジェクトのプロパティ値を、Cocoaバインディングのルートから外れた別ルートで直接いじった場合も大丈夫なんだろうか?

という単純な疑問から実験してみたもので、内容的にはかなりショボイので、「KVOを完全マスター」みたいな期待を持たれた方、ごめんなさい。m(__)m

今回作るサンプルのオブジェクト間の関連と操作のイメージ図を以下に示します。



1.改造内容の概略

今までいじってきたCocoaバインディングサンプルの画面の下に「KVO Test」というボタンと、テキストフィールドを追加し、「KVO Test」というボタンをクリックするとモデルデータの先頭エントリのタイトルをテキストフィールドの入力値に直接書き換えるというものです。

【元の画面】

【"KVO Test"ボタンクリック後】



2.UCAppControllerへのActionとOutletの追加と接続

UCAppControllerクラスのヘッダファイルに、実験用に追加したボタンのハンドラとテキストフィールドへアクセスするためのOutletを追加し、IB上で接続します。

●追加したハンドラとOutlet

【追加したOutlet】
// KVO試験用(変更値入力フィールド)
IBOutlet NSTextField    *_outNewValue;

【追加したボタンハンドラ】
// KVO試験用ボタンハンドラ
- (IBAction)onBtnKVOTest:(id)sender;

●ハンドラのリンク

●Outletのリンク



3.ボタンハンドラのコーディング

「KVO Test」ボタンのハンドラでは以下の処理を行います。

(1) Outlet経由でNSArrayController内のモデルデータ(content)オブジェクトを取得する
(2) (1)で取得したオブジェクトはUCBookを含むNSMutableArrayなので、その先頭のオブジェクトを取得する
(3) (2)で取得したオブジェクトに対してテキストフィールドに入力された文字列を指定してUCBookのsetTitleメソッドを呼び出し、プロパティ値の変更を行う

具体的な処理コードは以下の通りです。

// KVO試験用ボタンハンドラ
- (IBAction)onBtnKVOTest:(id)sender
{
    // NSArrayControllerが抱えているモデルデータオブジェクト取得
    NSMutableArray  *poContent = [_outBookCtrl content];
    if ( [poContent count] < 1 ) {
        return;
    }

    // 先頭のオブジェクトを取得
    UCBook  *poBook = [poContent objectAtIndex:0];

    // 末端のオブジェクトのプロパティ値をダイレクトに更新
    NSString    *pstrNewValue = [_outNewValue stringValue];
    [poBook setTitle:pstrNewValue];
}


4.ビルド&実行

以上で改造作業は完ですのでビルドして走らせてみてください。
自分が書いたコードでは特に何もしていないのに、末端のオブジェクトを直接変更しただけで自動的にビューも更新されるのは不思議な感じがしますね。

mkinoさんのHMDTサイトにあるAppleドキュメントの翻訳記事によると、以下のケースでKVOのメカニズムが起動されるそうです。

// アクセッサメソッドを呼ぶ
[self setName:@"Savings"];

// setValue:forKey: を使う。
[self setValue:@"Savings" forKey:@"name"];

// キー・パスを使う。account は document の KVC 準拠のプロパティ
[document setValue:@"Savings" forKeyPath:@"account.name"]

// インスタンス変数を直接代入する
name=[newSavingsName retain];

メソッド呼び出しはともかく、インスタンス変数への代入でもOKってのはすごいですね。いったい内部ではどうやって監視しているんでしょうか。興味があります。(でもフレームワークをhackして追いかけるほど元気も力もありませんが...(^_^;))

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

【作成・確認環境】
Mac OS X v10.3.4
Xcode v1.2

一覧に戻る