MacOSX10.3からNSKeyValueCoding(KVC)とNSKeyValueObserving(KVO)が実装されたことにより、MVCパターンの基盤が強化され、そしてKVCとKVOを応用したBindingも同時に実装されました。
KVCやKVOについては、その使用方法が十分に公開されています。しかし、Bindingに関しては闇な部分が多いです。
ADCのドキュメントによれば、独自のビューに Bindingを実装する場合、bind:toObject:withKeyPath:options:をオーバーライドするようにと書いてあります。
実装内容は単純で、まず、binding名が自身のものなのかチェックし、observableControllerオブジェクトに対してaddObserver:forKeyPath:options:context:を呼び出して自身をオブサーバーに設定、observableControllerとkeyPathはインスタンス変数に保存しておく、というものです。
自身の値が変化したときは、保存しておいたobservableControllerとkeyPathを使ってsetValue:forKeyPath:を呼び出し、相手の値が変化した時は、observeValueForKeyPath:ofObject:change:context:を使って通知を受け取ります。これで、双方の値が同期するようになります。
しかし、この方法には問題があります。それは、バインド項目が増えると、observableControllerとkeyPathを保存するためのインスタンス変数を沢山作らなければならないと言うことです。ついでに、bind:toObject:withKeyPath:options:のコード量も増えて実装が面倒になります。
でも実は、これに対する解決法は既に実装されていて、AppKitでBindが実装されているクラスも、MacOSX10.3になってインスタンス変数が増えている訳ではありません。
bind:toObject:withKeyPath:options:にはデフォルトの動作が実装されており、NSBinderや_NSBindingAdaptorなどのクラスが、Bindされる双方のオブジェクトの間に入って、面倒な部分を全部やってくれるようになっています。
NSBinderや_NSBindingAdaptorはCocoaの非公開クラスなので直接使うことはできませんが、その恩恵を少しだけ受ける方法があります。
@interface Foo : NSObject{
int value;
}
@end
@implementation Foo
+ (void)initialize{
[Foo exposeBinding:@"value"];
}
@endこのようにexposeBinding:メソッドでバインド名を登録すると、同名のKVC対応のキーが自動的にバインド対象となり、Bindできるようになります。
余計なインスタンス変数を作る必要も、bind:toObject:withKeyPath:options:やobserveValueForKeyPath:ofObject:change:context:メソッドを実装する必要もありません。とっても簡単です。
でも、この方法だと、相手のオブジェクトの値が変更されると自動的に自身の値が更新されるのですが、自身の値を変更しても相手の値が更新されないのです。型方向しか値の同期が取れないのです。
bindの双方向の値の同期は、別に双方向にKVOを使ってる訳ではないので、自身の値が変更になった場合は明示的に相手の値を変更する処理をしなければなりません。
でも今回のような手段を使うと、その辺りのAPIが非公開になってる訳で、どうにもなりません。
NSBinder関連を早く公開APIにしてほしいものです。非公開API使うのはちょっと怖いし。