-

なぜ Cocoa バインディングを使うのか?

-

MVC パラダイムのサポート

-

オブジェクトモデリング

-

バインディングの使い方

-

Master-Detail インタフェースの作成

-

バインディングを使った画像の表示

- Cocoa バインディング-
バインディングの使い方

この文章は、Cocoa Bindings: How to Use Bindings を翻訳したものです。

この記事は、いくつかの例を通して、アプリケーションでどのようにバインディングを使うかを説明します。Cocoa バインディングを使うと、複数のビューを実装できます。つまりユーザに様々な形で情報を見せることができます。この記事では、Cocoa バインディングを使うアプリケーションの作り方をステップごとに紹介します。また、実行時にデータを編集すると、Cocoa バインディングがどのように動作するかを説明します。

Cocoa バインディングは、あるオブジェクトに、他のオブジェクトのプロパティにアクセスしたり、変更を監視したりすることを可能にします。これは、アプリケーションのモデル、ビュー、コントローラオブジェクトの間を関連づけることで実現します。モデルとビューオブジェクトの間の関連付けは、通常コントローラオブジェクトによって行われます。ビューはコントローラを指し示し、コントローラはモデルを指し示し、モデルは実際のデータを保持します。これらの関連は、ビューとコントローラオブジェクトが提供する、バインディングをつなぐことで作られます。この記事では、それぞれのビューやコントローラが持つバインディングを個別に説明することはしないで、バインディングが一般的にどのように働くかを説明します。

MVC のオーバービューと、それが解決する問題については、「MVC パラダイムのサポート」を参照してください。キー・バリュー・コーディングの背後にある考え方と、Cocoa バインディングのドキュメントで使われる用語については、「オブジェクトモデリング」を参照してください。

目次:

問題
解決方法
バインディングの動作

問題

Cocoa バインディングが解決する問題、うんざりする退屈で重複するつなぎのコードの問題、は、具体的な例を使って説明するのが一番いいでしょう。この記事では、架空のメディアコンテンツ管理アプリケーションを、ユーザインタフェースとオブジェクト指向のデザインの例として使います。

図 1 のような、メディアコンテンツ管理アプリケーションの、ユーザインタフェースを考えましょう。このアプリケーションでは、ユーザは、テーブルビューのオブジェクトリストからオブジェクトを選んで、選択されたオブジェクトは下のボックスに表示されます。このスタイルのユーザインタフェースは、Master-Detail インタフェースと呼ばれます。ここでは、テーブルビューが Master インタフェースで、ボックスに含まれているビューが Detail インタフェースです。

このユーザインタフェースを動かすには、データを複数のビューで表示できるようにしなくてはいけません。編集可能なデータもありますし、そうでないものもあります。テーブルビューで選択された行と、Detail インタフェースは、同じデータを表示することに注意してください(メディアコンテンツの画像、タイトル、著作者)。このインタフェースでは、コンテンツをテーブルの行でも Detail インタフェースでも、編集できます(たとえば、タイトルの変更)。この編集は、編集されたデータをどこかに保存することだけでなく、ユーザが編集したときに両方のビューで表示することが必要になります。もう一方のビューは、更新されないといけないのです。

Master-Detail インタフェース

また、図 2 のような、選択された行の著作者を表示する人物インスペクタを開けることにしましょう。人物インスペクタは、著作者に連絡を取るための情報を編集できるようにします。この時点で、著作者に関しては 3 つ以上のビューが画面上にあることになります。著作者は、Master インタフェースのテーブルの行、Detail インタフェースのポップアップメニュー、人物インスペクタに現れます。ユーザが著作者の名字に間違いがあって、それを変更することを考えましょう。アプリケーションは、この変更を保存して、他のすべてのビューを同期する必要があります。

メディアコンテンツ管理アプリケーション

複数の、さらに編集可能な複数のビューの動機の問題は、このタイプのアプリケーションでは何度も何度も起こります。ユーザが、選択されたオブジェクトの Detail インタフェースに、新しい画像をドラッグしたとしましょう。アプリケーションは、Detail インタフェースと Master インタフェースの両方を更新する必要があります。選択された行に表示されているアイコンを更新するのです。

また、ユーザに、Master インタフェースからもオブジェクトの追加と削除を許可したいと思うでしょう。この仕様は、特にアプリケーションを初めて起動して、オブジェクトが 1 つもないときに必要になります。たとえば、図 2 の Master インタフェースにボタンを追加すれば、ユーザがメディアコンテンツを追加、削除できます。また、図 3 のように、すべての著作者をテーブルビューに表示して、著作者を追加、削除するためのボタンを追加することもあるでしょう。

著作者のリスト

解決方法

Cocoa バインディングは、上で述べたように何度も起きる問題を解決する、MVC デザインパターンを実装します。この MVC の実装は、Foundation のプロトコルを使います。特に、キー・バリュー・コーディングと、キー・バリュー・オブザービングです。この抽象化のおかげで、実装の詳細を知る必要がなくなります。つまり、Cocoa バインディングを、モデル、ビュー、コントローラオブジェクトの間のメッセージの正確な流れを知らなくとも、使うことができます。これを使うために知っておかなくてはいけないことは、このメカニズムを利用するためにどのようにクラスを変更するか、通常はいくつかのプロトコルを適合すること、だけです。したがって、この記事は Cocoa バインディングアプリケーションを実装するステップを紹介することから始めて、次に例を通して、実行時にバインディングがどのように動作するかを説明します。

モデルの作成

まず、モデルクラスのデザイン、実装が必要になります。これは、データをカプセル化するオブジェクトと、そのデータを操作するメソッドになります。

このモデルを Cocoa バインディングで使うようにするには、モデルのプロパティに対して、アクセッサメソッドかインスタンス変数を提供して、KVC 準拠にすればいいのです。また、それぞれのプロパティに対して、妥当性を検出するメソッドも実装するべきでしょう。KVC 準拠については、キー・バリュー・コーディングを参照してください。

Foundation は、モデルを実装するための道具を提供します。モデルは、通常 Cocoa アプリケーションのルートクラスである、NSObject を継承します。これは、すでにキー・バリュー・コーディングに準拠しています。また、Foundation は、文字列、数値、バイナリデータクラスといった、モデルの属性を実装するために必要な基本的なクラスを、すでに持っています。NSArray や NSDictionary といった、対多関連を実装するためのコレクションクラスもあります。

たとえば、メディアコンテンツ管理アプリケーションでは、図 4 のようにオブジェクトをデザインします。この図は、オブジェクト図(クラス継承図ではなく)で、実行時でのオブジェクトのインスタンスの関係を示しています。この例では、モデルクラスは Media、Person、Phone、Address で、NSObject を継承しています。したがって、すでにキー・バリュー・コーディングと、キー・バリュー・オブザービングに準拠しています。そして、Media クラスには、プロパティにアクセスするために、titlesetTitle: というメソッドを実装するでしょう。同様に、Person オブジェクトには、対一関連のオブジェクトを返す、author というメソッドを実装します。対応する setAuthor: メソッドは、渡されたオブジェクトを author に設定します。

注意:属性のコピーを作ったり、コピーを返すアクセッサメソッドを作ることは、いい練習になります。Media の setTitle: メソッドでいうと、これは引数をコピーして保存します。ですが、対一関連はコピーしたり、コピーを返したりしてはいけません。author アクセッサメソッドは、コピーではなくて、実際の対一関連のオブジェクトを返すべきです。

Cocoa ドキュメント・ベース・アプリケーションでは、Media オブジェクトの集合や Person オブジェクトの集合を含む NSMutableArray のインスタンス変数を、MyDocument に追加するでしょう。これらの配列は、ドキュメント・ベース・アーキテクチャで、「ドキュメント」になります。これらの配列と、その中にあるオブジェクトは、アプリケーションが終了した後も保存しておきたいものです。

オブジェクトモデルの例

ビューの作成

MVC の観点からは、ビューは、編集可能でもある、モデルのデータを表示します。Cocoa では、ビューは、特に NSView を継承するオブジェクトを指します。ですがこのドキュメントでは、ビューの定義を広げます。ここでは、Cocoa のビュー、ウィンドウ、そしてテーブルビューが持つセルを含むペイン、これは NSView を継承していません、を含みます。Cocoa はすでに、アプリケーションを作るときに使うことができる、再利用可能なビューの提供という大きな仕事を成し遂げています。

ユーザインタフェースの多くは、すでに存在する NSView のインスタンスから作られます。通常、Interface Builder を使って、それらのビューをウィンドウやペインに追加することで、ユーザインタフェースを作ります。たとえば、メディアコンテンツ管理アプリケーションでは、タイトル、作成された日付、著作者、コメントなどの情報を編集するための、Detail インタフェースを作るでしょう。この例では、ユーザインタフェースはとてもシンプルです。図 1 の Detail インタフェースのように、メディアオブジェクトのタイトルをテキストビューに表示して、ポップアップメニューに著作者を表示して、画像をイメージビュに表示するだけです。

Cocoa バインディングは、モデルに対して複数のビューをつくり、データが変更されたときに自動的にそれらを同期することをサポートします。たとえば、図 1 の Master-Detail インタフェースでは、著作者の名前は、テーブルビューと、Detail ビューのポップアップメニューの両方に表示されます。また、Cocoa バインディングを使うと、ユーザインタフェースは、単一のモデルに縛られません。複数のさまざまなモデルに対して、編集可能なビューを表示することができます。ユーザインタフェースをデザインする際には、アプリケーションのデータをさまざまな方法で表示する、自由があります。

また、アプリケーションに特化した、NSView をサブクラス化して、独自のビューを作ることもできます。もし独自のビューを作る必要があるときは、他のオブジェクトに使用してもらいたいバインディングを外部に公開することだけが、必要になります。バインディングの公開は、キー・バリュー・バインディングメカニズムで利用するために、すべてのビューで必要になります。バインディングの公開に関する詳細は、NSKeyValueBindingCraetion 非形式プロトコルを見てください。

コントローラの作成

モデルとビューの間のすべての通信は、コントローラオブジェクトを通ります。コントローラは、ビューが表示するデータをつかまえて、ビューがそのデータを表示する前に、値の変換のような複雑の動作を提供できます。また、コントローラは、ビューによって編集されたデータがモデルに格納される前につかまえて、同様に値の変換をすることができます。

ビューとモデルの間の通信を取り扱うことに加えて、コントローラはモデルとビューの同期も行います。アプリケーションで、1 つのモデルに対して複数のビューがあったならば、ユーザがあるユーザインタフェースでモテルのプロパティを編集したら(たとえば、テキストフィールドでテキストを編集したり、ポップアップメニューからアイテムを選択したり)、対応するモデルは変更されて、他のビューが更新される必要があります。キー・バリュー・コーディングを使うことで、コントローラはこの問題に対して、エレガントで意識しないで使える解決方法を提供します。

Cocoa は、いくつかのそのまま使えるコントローラを提供しています。それらは、Inteface BUilder のパレットにあります。提供されているコントローラのサブクラスを作ることで、独自のコントローラを作ることもできます。これらのクラスは、モデルとビューの間のつなぎのコードを実装しています。

NSController
すべてのタイプのコントローラクラスのための、基本的な機能を定義した、抽象親クラスです。
NSObjectController
単一のオブジェクトを管理するコントローラです。
NSArrayController
オブジェクトの配列を管理するコントローラです。配列の中の 1 つ以上のオブジェクトを選択可能です。
NSUserDefaultsController
ユーザデフォルトデータベースのために特にデザインされたコントローラです。

Inteface Builder の Controller パレットから nib ファイルへ、NSObjectController か NSArrayController か NSUserDefaultsController をドラッグするだけで、コントローラを作ることができます。もし単一のモデルオブジェクトしか持たないならば、NSObjectController で十分でしょう。もし NSArray か NSMutableArray に入っているオブジェクトの集合を持つならば、NSArrayController を使う必要があるでしょう。いくつかのケースでは、独自のコントローラを実装したくなるでしょう(独自の配列コントローラでフィルタを使うで、NSArrayController のサブクラスの例を説明します)。

一度コントローラを作たっら、どの種類のオブジェクトを取り扱うかを教える必要があります。また、そのオブジェクトが編集可能かどうかを指定できます。モデルが編集不可ならば、ビューは編集できません。モデルが編集可能ならば、コントローラはモデルのクラス名を知る必要があります。そうすれば、モデルオブジェクトが存在しないときに、作ることができます。通常この情報は、図 5 のように、Interface Builder の Attribute ペインで設定できます。

NSArrayController の Attribute ペイン

たとえば、図 6 に示されているようなアプリケーションを実装するには、Media オブジェクトの集合を管理する、NSArrayController が 1 つ必要でしょう。そして、Person オブジェクトの集合を管理するために、別のコントローラが必要です。Detail インタフェースは、選択されたオブジェクトのプロパティを表示するだけなので、Master インタフェースと同じ NSArrayController を使えるでしょう(配列コントローラは現在の選択を管理します)。もし同時に複数の人物インスペクタを開きたいならば、表示されている Person オブジェクトごとに、NSObjectController が必要になるでしょう。

メディアコンテンツアプリケーションのコントローラ

モデル、ビュー、コントローラをバインディングする

ここまでで、すべてのモデル、ビュー、コントローラオブジェクトは、インタフェースの定義を持つか、プロパティを公開していますが、これらの間に関係は存在していません。Cocoa バインディングは、あるオブジェクトのプロパティから、他のオブジェクトのプロパティに関係を作るために、3 つのプロトコルを利用します。キー・バリュー・コーディング、キー・バリュー・オブザービング、キー・バリュー・バインディングです。このメカニズムは、こちら側のオブジェクトのプロパティが変更されたら、あちら側のオブジェクトのプロパティを自動的に更新します。この関係、つまりあるプロパティを他のプロパティにバインディングすること、は、Inteface Builder で、オブジェクトのバインディングを設定することで作ることができます。これをプログラム上で実装することもできますが、この記事の例では Interface Builder を使います。

モデル、ビュー、コントローラの間の関係は、コントローラとビューのバインディングを設定することで作ります。コントローラとビューは、そのクラスごとに、複数のバインディングを持つことができます。バインディングは、設定できるいくつかの特性を持ちますが、すべてのバインディングは、Inteface Builder で表示される、次の 3 つの主要な特性を持ちます。

  • Bind to—バインディングされるオブジェクト。通常はコントローラかモデルオブジェクトです。
  • Controller Key—バインディングされるオブジェクトのプロパティを指定します。通常はモデルオブジェクトになります。
  • Model Key PathController Key の値のプロパティか、Controller Key が空白ならばバインディングされるオブジェクトのプロパティを指定します。

これらの特性をバインディングに設定することで、モデル、ビュー、コントローラ間での変更を伝達するためのパイプを作ることになります。

バインディングは、ビューにフォントや色を指定するようにシンプルです(たとえば、NSTextView の fonttextColor バインディング)。また、バインディングは、テーブルビューの列にデータを指定するほど、複雑になることもできます。ですから、セルの編集は、実際にはその下にある、対一関連のモデルの値を変更しているのです。

Interface Builder で選択して、Info ウィンドウの Bindinds ペインを開くことで、ビューのバインディングを表示することができます。Interface Builder は、パレットからドラッッグできるユーザインタフェースのほとんど全てと、コントローラのようにいくつかの非ユーザインタフェースのための、Bindings ペインを持っています。図 7 は、NSWindow の Bindings ペインです。

NSWindow の Bindings ペイン

コントローラをモデルにバインディングする

まず、コントローラのコンテントを指定することで、コントローラをモデルにバインディングします。このやり方は、コントローラに依存します。オブジェクトコントローラの場合は、単にコントローラの content アウトレットを、管理するオブジェクトに結びつけるだけです。アプリケーションがモデルを 1 つだけ持つ場合は、content アウトレットをモデルオブジェクトに結びつけるだけです。

配列コントローラの場合は、コントローラの contentArray バインディングを設定することで、コンテントを指定します。図 6 に書かれている Master-Detail インタフェースでは、C1contentArray バインディングを、アプリケーションが持つ Media オブジェクトの配列に結びつけます。次のようにします。

  1. Bind to 特性を、Media オブジェクトの配列を提供するオブジェクト(たとえば File's Owner)に結びつける。
  2. Controller Key を空白のままにする。
  3. Model Key Path に Media オブジェクトの配列の名前を設定する。

図 8 は、contentArray バインディングが表示されている、Interface Builder での NSArrayController の Bindings ペインです。

NSArrayController のバインディングペイン

同様に、C3contentArray バインディングを、アプリケーションで定義されている著作者の配列につなぎます(たとえば、ドキュメント・ベース・アプリケーションでの MyDocuments のインスタンス変数として定義されているでしょう)。

デフォルトでは、コントローラに管理されているオブジェクトは編集可能です。配列コントローラの場合は、コントローラはオブジェクトを追加、削除できます。このデフォルトの動作を変更したいときは、Interface Builder でコントローラの Attribute ペインを使います。特に、ユーザにモデルや集合を変更してほしくないときは、Editable のチェックボックスを外します。

ビューをコントローラにバインディングする

次に、それぞれのビューのバインディングを設定することで、ビューをコントローラに接続します。それぞれのビューが公開しているバインディングは、それぞれのビュー特有のもので、この記事でカバーするには多すぎます。ですが、コンテントを表示するタイプの多くのビューは、value と呼ばれるバインディングか、コンテントを指定する方法が複数ある場合は、value という単語を含むバインディングを持っています。

たとえば、図 9 のような人物インスペクタを実装するには、テキストフィールドを、人物の名字、名前、電話番号のプロパティに結びつけます。ここでは、1 つのオブジェクトコントローラと、人物インスペクタウィンドウごとに、1 つの Person オブジェクトがあるとします。ビューをコントローラに結びつけるには、Interface Builder で、それぞれのテキストフィールドの value バインディングを表示します。そして、次のように特性を設定強います。

  1. Bind to 特性に、コントローラオブジェクトを設定します。
  2. Controller Key 特性に selection を設定します。コントローラが管理しているオブジェクトです。
  3. Model Key Path 特性に、表示したいモデルのプロパティを設定します(図 9 では、テキストフィールドは、上から順に、fisrtNamelastNamephone.work を表示します。)。

人物インスペクタ

配列コントローラへのバインディングは、もっと複雑です。テーブルのそれぞれの列の value バインディングを、表示するプロパティにつなげます。たとえば、図 6 の二列目は、File's Owner が管理している Media オブジェクトの title プロパティを表示しています。この場合、テーブルカラムの value バインディングの、Bind to 特性を、配列コントローラオブジェクト(C1)に設定し、Controller Key をコントローラの arrangedObjects プロパティ、コントローラが管理しているオブジェクトの配列、に設定します。そして、Model Key Path は、その列に表示したいプロパティに設定しあmす。

図 6 の Detail インタフェースは、選択されたオブジェクトを表示するように設定されています。これは、value バインディングの Bind to 特性に同じ配列コントローラを設定し(上で説明したように)、Controller Kye 特性に selection、Master インタフェースで現在選択されているオブジェクト、を設定します。そして、Model Key Path 特性には、ビューに表示したいプロパティを設定します。

モデル、ビュー、コントローラの間のバインディングを設定すれば、アプリケーションをビルド、実行する準備が整ったことになります。

バインディングの動作

アプリケーションを初めて起動したときは、表示するモデルが 1 つもないでしょう。もしビューがモデルを持っていないならば、デフォルトではプレースホルダが表示されます。たとえば、Detail インタフェースのテキストフィールドは、Master インタフェースでオブジェクトが選択されていなければ、No Selection を表示するでしょう。

ドキュメント・ベース・アプリケーションでは、通常モデルは、前に保存したファイルかデータソースから読み込まれます。また、追加と削除ボタンを実装して、ユーザが最初のコンテンツを作ることができるようにすることもできます。配列コントローラは、この動作を実装するために、add:remove: メソッドを提供しています。

ビューに変更を行う

さて、コントローラとモデルに関連づけられているテキストフィールドに対して、文字の入力を行うと、何が起きるのでしょう?テキストフィールドの値は、コントローラキーに結びつけられているので、ユーザが編集を終えるとコントローラにメッセージを送ります。テキストフィールドの設定によっては、これはユーザがタブで次のフィールドに移動したときに起きるでしょう。コントローラは、この編集をモデルに送る前に、処理を行うことがあります。バインディングの設定によって、NSValueTransfomer が、編集内容を他の値に変換するでしょう。たとえば、ユーザインタフェースが、数値のプロパティを、適切な文字列で表示することなどがあります(区切り線を含む、社会保障番号のように)。value バインディングは、値変換を使って文字列を数値に変換したり、その逆を行うように、設定することができます(値変換の例については、独自の値変換を作るを参照してください)。

図 10 のように、ユーザが人物インスペクタで、著作者の名字を変更するとしましょう。C2 コントローラは、編集されたことを通知されて、値変換が設定されているならば、値を変換します。そうでなければ、コントローラは setLastName: メッセージをモデルに送ります。

ビューに変更を行う

モデルに変更を行う

モデルは、どのように複数のビューを更新するのでしょう。モデルをユーザアクション以外で変更することも可能ですが、多くの場合はユーザがビューのコンテントを編集して、このアクションが、同じプロパティを持つビューに対するモデルの更新のトリガーになります。

たとえば、図 11 では、4 つのビューが同時に同じ Person オブジェクトを表示しています。Master-Detail インタフェースで名字が編集不可だとしても、名字が変更されたらビューは更新しなくてはいけません。Person オブジェクトの setLastName: を呼び出すことが、lastNmae モデルキーを使ってコントローラにバインディングされている、すべてのオブジェクトに変更メッセージを送るトリガーになります。再び、コントローラは、変更メッセージをとらえて、もし設定されていれば、値変換をすることができます(値変換の例については、パスを変換する値変換を作るを参照してください)。そうでなければ、コントローラは、メッセージをそれぞれのビューに転送します。

いったんそれぞれのビューにバインディングを設定してしまえば、この更新メカニズムを意識することはありません。実装の詳細を知る必要はありません(どちらにしろ非公開です)、メカニズムはただ単に動くのです。ただ、モデルのセットメソッドを呼ぶことは、すべての関連するビューの更新のトリガーになる、ということを知る必要があります。もしビューをプログラムで更新する必要があるならば、セットメソッドを使ってください。プロパティを更新する他のメソッドを使ったり、インスタンス変数に直接アクセスすることは、変更メッセージを送るトリガーになりません。

複数ビューの更新


[Home] [Download] [Archives] [BBS] [Cocoa Programming Tips 1001] [Core Foundation の秘密] [Safari Developer Center] [はじめてのブラウザのつくり方] [Sketch BP] [スクリーンセイバーを作ろう] [Objective-C 最適化] [Authorization API 完全理解] [Mac OS X Programming Books Review] [オブジェクト指向の言語比較論] [panther-dev]

mailto: mkino@xd5.so-net.ne.jp