先日Apple社から、iPhone開発者に向けて、いままでiPhone SDKに課されていたNDAの条項を解除する予定であることが発表された。いままではNDAの制限により、公の場でiPhone開発に関わる議論したり、関連書籍の発売などが出来なかった訳だが、ようやく解禁ということらしい。

iPhone開発が本格化してくると、これからObjective-Cを始めるプログラマも増えてくるだろう。ジワジワとObjective-Cに風が吹いてくるのが感じられるようになった。

さて、今回も引き続きObserverパターンの話だ。今回は、キー値監視を取り上げる。

Cocoaバインディングの基礎技術

FoundationフレームワークとApplication Kitフレームワークが、Mac OS Xの登場ともにCocoaとして生まれ変わってから、いくつかの大きい変化があった。その中の1つが、PantherことMac OS X 10.3で行われた、Cocoaバインディングの導入だろう。

Cocoaバインディングの一番の目的は、アプリケーションの開発効率を高めるというものだ。そのために、アプリケーションのMVCの構造を見直し、汎用的に使えるコントローラクラスが提供され、MVCの間の結びつきは極限まで弱められた。この、「結びつきを弱める」ためにいくつかの基礎技術が投入されたのだが、これがCocoaフレームワークに大きな影響を与えることになった。

それは、「キー」という概念で、オブジェクトの属性値を操作しようという考え方が根底にある。キーを指定して属性値を取得/設定するための、「キー値コーディング(Key Value Coding、KVC)」。キーを指定して、その属性値が変化したかどうかを監視する「キー値監視(Key Value Observing、KVO)」。そして、これらを組み合わせた、モデルの値の変更を自動的にビューに反映させる「キー値バインディング(Key Value Binding、KVB)」だ。

この3つが、Cocoaバインディングを支える基礎技術となる。この中のキー値監視が、Observerパターンとしてとらえることが出来るのだ。今回は、これを紹介しよう。

キー値監視のためのメソッド

読者の中には、Cocoaバインディングを使ったことはあるが、キー値監視のことは知らない、という方もいるだろう。というのは、実際のアプリケーション開発でCocoaバインディグンを使うときは、ほぼすべての設定をInterface Builder上でグラフィカルに行う。これによりソースコードの記述量を減らし、改変も行いやすくなるため、開発効率が高まる、という訳だ。

だから通常は、キー値監視のソースコードを書く必要はない。だがもちろん、あえて手で書くことも出来る訳だ。ここでは、キー値監視で使われるメソッドを説明していこう。

まず、登場人物を整理しよう。Observerパターンでは、ObserverとSubjectがいる訳だが、キー値監視では、実は、これらは何でもいい。というのは、キー値監視に必要なメソッドの実装は、ルートクラスであるNSObjectで行われているのだ。したがって、すべてのオブジェクトがObserverになりうるし、Subjectにもなりうるのだ。

キー値監視で使われるメソッドは、NSKeyValueObservingプロトコルで定義されている。その中から、必要なメソッドを呼び出したり、または自分のクラスで上書きすることになる。

Observerパターンで最初に使うのは、SubjectにObserverを登録するメソッドだ。これは、addObserver:forKeyPath:options:context:というものになる。

List 1.

- (void)addObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options context:(void*)context

4つの引数があるが、重要なのは最初の2つだ。1つは、Observerとなるオブジェクト。そして2つめは、監視を行うキーになる。つまりこの監視は、キーで指定される属性値が変更されたかどうかを監視しているのだ。Observerの登録を行うメソッで、同時に何を監視するかも指定しているのだ。

Subjectとなったオブジェクトは、監視されている値が変更されたら、Observerに対して通知を行う。これは、実は自動的に行われる。というのは、キー値コーディングによって、キーを指定して値を設定するsetValue:forKey:というメソッドが用意されているのだが、この中で通知が行われるのだ。したがって、プログラマから見れば、特に何もしなくとも通知が行われることになる。

もちろん、自分の手で通知を行う方法も用意されている。キー値コーディングでは、キー値の設定を自分で実装したアクセッサメソッドで行うことも出来る。特別な処理をしたいときは、この手法を使う。そしてこの場合は、手作業で通知を行う必要があるのだ。そのために用意されているのが、willChangeValueForKey:、didChangeValueForKey:メソッドだ。

List 2.

- (void)willChangeValueForKey:(NSString*)key
- (void)didChangeValueForKey:(NSString*)key

これらのメソッドを呼ぶと、それぞれObserverに通知が行われる。willChangeValueForKey:は値の変更を行う直前に、didChangeValueForKey:は変更の直後に、呼び出すのがマナーだ。

Observerとなったオブジェクトでは、通知を受け取る。そのために用意されているメソッドは、observeValueForKeyPath:ofObject:change:context:だ。

List 3.

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

Observerでは、このメソッドを上書きして通知を受け取る。1つめの引数が、変更が行われたキー。2つめが、Subjectのオブジェクトになる。3つめの引数には、変更の種類や、変更される値が入っている。さきほどの、willChangeValueForKey:と、didChangeValueForKey:の違いは、ここで判断するのだ。

汎用性の高いObserverパターンの実装

キー値監視は、Cocoaフレームワークの奥底に組み込まれたObserverパターンと言ってよい。NSObjectのレベルで実装されているので、すべてのオブジェクトがこの恩恵を受けることが出来る。

これを、Observerパターンとして評価してみよう。まず、1対多の通知が出来る。ObserverとSubjectの結びつきは、非常に弱い。そして、ルートクラスに組み込まれているし、自動的な通知も備えているので、非常に手軽に使える。Observerパターンとしては、非常に汎用性が高いと言えるだろう。

その反面、手軽に使える分注意しないといけないこともある。標準では、値が変更されるために通知が行われるので、気がつくと通知が多数飛び交い、パフォーマンスを落とすことがある。この場合、カスタムのアクセッサメソッドを実装して、通知の乱発を抑えるような実装をする必要がある。

また、NSNotificationと比べた場合、通知を受けるメソッドを指定することが出来ない。すべて、組み込みのobserveValueForKeyPath:ofObect:change:context:を使うことになる。多くの監視を行うと、1つのメソッドで処理をしなくてはいけないので、不利だ。

これが、Cocoaの根底に組み込まれたObserverパターンであるキー値監視だ。