2010-12-07

Cocoa Bindings の肝は KVC 準拠だ

ようやく Cocoa Bindings の謎が解けたように思う。少なくとも、Cocoa Bindings を使ったアプリを作るための「心得」のようなものを見つけることができた。以下で、それを簡単に説明してみる。

用語の整理

まずは用語の整理。Cocoa Bindings 関連のドキュメントを読むためには以下の KVC の用語とそれが意味する概念を理解しておく必要がある。

KVC (Key-Value Coding) とは、オブジェクトが持つプロパティに対してキーとなる名前を介してアクセスすること、およびそのためのオブジェクト側の実装のことだ。「KVC でアクセスする」とか「このクラスは KVC に準拠させてある」などと使う。

プロパティ(property)とは、オブジェクトがその内部に抱える「何か」のことで、典型的にはオブジェクトの内部状態を示す様々な値のことだ。ただし、プロパティとはオブジェクトの外部から見えるもののことで、必ずしもプロパティの値そのものが内部で保持されているとは限らない。プロパティとはオブジェクトのインタフェースのうち「何か」を返すものと言っても良い。

プロパティはその値とオブジェクトとの関係によって、以下の 3 つに分けられる。

  • 属性(attribute)
  • 対一関係(to-one relationship)
  • 対多関係(to-many relationship)

属性(attribute)とはプロパティのうち、Objective-C で言う単純型のことで、数値や文字列の他、NSColor や NSNumber のような不変オブジェクトもふくまれる。

対一関係(to-one relationship)とは、プロパティがそれ自身のプロパティを持ったオブジェクトの場合を言う。以下に示す、AppController のプロパティ entry がこれにあたる。

@interface Entry : NSObject {
    NSString *title;
    NSString *content;
    NSDate *lastUpdateDate;
}

@interface AppController : NSObject {
    Entry *entry;
}

対多関係(to-many relationship)とは、簡単に言えばプロパティが配列のような集合型になっている場合のことだ。以下の Feed のプロパティ entries がこれにあたる。

@interface Feed : NSObject {
    NSMutableArray *entries;
}

KVC 準拠

あるクラスを(正確にはあるクラスの特定のプロパティを) KVC 準拠にするためには、いくつかの要件を満たさなければならない。これは、要は、プロパティに対するアクセス用メソッドを一定の規則による名前で作る、ということだ。

たとえば、title という文字列のプロパティを持ったクラスの場合なら、以下の 2 つのメソッドを定義する。

- (NSString *)title;
- (void)setTitle:(NSString *)aTitle;

この 2 つがあるなら、必ずしも属性を保持するインスタンス変数を定義する必要はない。

加えて、プロパティへのアクセスにはすべて(クラスの内部でも) KVC 方式を用いる。これは準拠に必須ではないが、こうすることで KVO が正しく機能する。KVC 方式とはインスタンス名に「.」(dot) でプロパティ名を連結した記法のことだ。たとえば、先のクラスのインスタンス名が entry なら、entry.title としてアクセスする。クラス内部では、インスタンス名として self を使えば良い。つまり、self.title で読み書きする。

対一関係の場合も同様だ。さらに、この場合、プロパティも KVC に準拠しているなら「.」でつなげてアクセスする。たとえば、self.entry.title のように。

対多関係の場合の追加

一方、対多関係の場合はプロパティが配列(の類)であるため、その要素にアクセスするためのメソッドも必要になる。最初に挙げた Feed の場合、以下のようなメソッドを定義する必要がある(要素として Entry を抱えると想定)。

// for KVC compliance (required)
- (void)insertObject:(Entry *)entry inEntriesAtIndex:(NSUInteger)index;
- (void)removeObjectFromEntriesAtIndex:(NSUInteger)index;
- (void)replaceObjectInEntriesAtIndex:(NSUInteger)index withObject:(Entry *)entry;
// for KVC compliance (optional)
- (int)countOfEntries;
- (Entry *)objectInEntriesAtIndex:(NSUInteger)index;

これらのメソッド名にはプロパティ名が埋め込まれている。たとえば、insert の場合、insertObject:in<key>AtIndex: の key のところにプロパティ名の entries が埋め込んである。他も同様だ。

KVO 準拠

KVO 準拠で何より大事なことはプロパティのアクセスはすべて KVC 方式で行う、ということだ。たとえば以下は、ここ数日、悩まされてきたエラーメッセージだ。このエラーの原因が、あるクラスで自分自身のプロパティへのアクセスに KVC 方式を使っていなかった(部分があった)ことにあった。

(実行時のログより)
Cannot update for observer <NSAutounbinderObservance 0x113761290> for the key path "feed.entries" from <PreferenceController 0x11375cc50>, most likely because the value for the key "feed" has changed without an appropriate KVO notification being sent. Check the KVO-compliance of the PreferenceController class.

具体的には、以下のようなコードになっていた。本来、reload の中で newArray をプロパティにセットした際に、これを監視しているオブジェクトたちに通知が飛ぶことになる。ところが、reload が実行される前に awakeFromNib の中で KVC 方式を使わずに entries を変更してしまっていたため、この通知が送れなくなっていた。それが上のエラーだ。awakeFromNib の中でも、reload と同様に self.entries という表記を使うようにすることで、この問題は解消した。

@implementation AppController
[...snip...]
- (void)awakeFromNib
{
    [...snip...]
    entries = [[NSMutableArray alloc] init];
    [...snip...]
}

- (IBAction)reload:(id)sender
{
    [...snip...]
    self.entries = newArray;
    [...snip...]
}

また、対多関係プロパティの場合、プロパティへの要素の追加・変更・削除では、先述のように KVC 準拠のために定義したメソッドを使わなければならない。

まとめ

以上が、ここ数日、悩まされてきた問題に対する一応の解答になる。まとめると、Cocoa Bindings を使ったアプリを作る上で肝心なことは、とにもかくにも KVC 準拠ということになる。

そして、KVC 準拠でクラスを作ろうと思うなら徹底して KVC 準拠にすることだ。特定のプロパティだけを KVC 準拠にしようなんて中途半端なことを考えていると、抜けが出てくる。ひとつのクラスの一部は KVC 準拠で他は違うというような状態では、(プログラムが動かないとわかってから)準拠の抜けを探すのは難しい。デバッグで苦労するよりも、コーディング中に少し余分に気を使う方が良い。

Cocoa Bidings を使ったアプリ (MVC アーキテクチャを想定) を作るなら、まずはモデルのクラスをを KVC 準拠で作ること。また、モデル内部の他のメソッドでも、モデルオブジェクトを扱うコントローラの実装でも、とにかくモデルのプロパティへのアクセスには KVC を使うこと。対多関係を使うなら(モデルが配列等を内部に抱えるなら)、内部の配列に直接アクセスするのではなく、insert や remove、replace といったメソッドを KVC 準拠の形式で定義すること。

あとひとつ付け加えるとすれば、やはり Interface Builder に対する慣れだろうか。インスペクタで GUI 部品の設定をあちこちいじっていると、どこをどう変更したのかがわからなくなってくる。どうしても動かなくて困っていたところ、部品を一度削除して最初から設定し直すと動いた、なんてことが時々起こる。こればかりは慣れるしかないと思う。部品を汎用に作ろうとすれば設定項目を増やさざるを得ず、設定項目が増えればそのためのツールは複雑になるものだから。

参考文献

詳解 Objective-C 2.0
荻原 剛志
ソフトバンククリエイティブ ( 2008-05-28 )
ISBN: 9784797346800

関連リンク

関連記事

0 件のコメント:

コメントを投稿