2003年12月28日

nibファイルを分ける

忘れちゃうので覚え書き。

例えばMail.appなんかだとnibファイルが50近くに細かく分けられている。
nibファイルを分けると何がいいか、っていうのは、ほんとのとこよく分かんないんだけど、個人的には

  1. アプリケーションの起動が早くなる。メインバンドルにすべてを突っ込んでしまうと、その中のオブジェクトをすべて読み出して再生するのに時間がかかる(ってアーロン・ヒレガスの本に書いてあった)
  2. nibが分かれていることで再利用しやすいモデル・ビュー・コントローラを設計できそうな気がする

で、まぁnibファイルを分けてみよう!ってことになった場合に困るのが、nibファイル間でconnectできねってこと。

例えば、Cocoaドキュメントベースアプリケーションの雛形を選んでプロジェクトを作成するとMainMenu.nibとMyDocument.nibっていう2つのnibファイルができる。
MainMenu.nibにはメインメニューがあって、MyDocument.nibにはドキュメントウィンドウがある。
MyDocumentクラスが例えばドキュメント中の選択してるオブジェクトの種類によって、受け付けるメニューコマンドを取捨選択できたら便利じゃない?
けど、MainMenu.nibにあるMainMenuからはMyDocument.nibのアイテム(ウィンドウその他のオブジェクトやインスタンス化されたオブジェクト、それにFile's OwnerやFirst Responder)に向けて線を引っ張ることはできない。

ところが、Mail.appはメインのメール一覧画面とフォルダ用のドロワー画面が別のnibファイルになってて、もちろんメインメニューもそれぞれのnibにあるわけじゃなくて雛形同様、一個のnibにある。
けど、フォルダ用のドロワー画面はメインメニューの「メールボックス表示」からのメニューコマンドを受け取って、NSDrawer#toggle:を受け取ってる。

これ、どうやってるのつって調べてみると、「メインメニューから、メインnibファイルのFirst Responderに向けて線を引っ張ってる」。

Mail.appのMailViewer.nib(Mail.appのメインnib)のFirst Responderにはなぜか「showMailBoxesPanel:]っていうアクションがある。
確かにshowMailBoxesPanel:っていうアクションがFirst Responderにあれば。レスポンダチェーンをたどって、異なるnibファイルにあるオブジェクトにもメッセージが届くだろうと想像できる。レスポンダチェーンにshowMailBoxesPanel:を処理できるオブジェクトがいなければ、そのメッセージは無視されるだけ。

ほんでも、なぜMailViewer.nibのFirst ResponderにはshowMailBoxesPanel:っていうのあるの?なぜワタシのMainMenu.nibのFirst Responderにはないの?これどういうことよ?

いろいろ調べた結果、Interface Builderの「Class」タブ画面で、NSObjectにぶら下がっているFirst Responderには手動でアクションを追加できるということが判明。
なんかね、いろいろファイル読み込ませたり、色んなボタン押しまくったりしたんだけど、分かっちゃうとなーんだって感じ。

この「Class」タブ画面では、クラス?によって、動きが3種類に分かれる。


  • NSObjectなどの既存クラス:グレーで表示される。派生クラスの作成ができる。アウトレットは追加できない。アクションは追加できる(カテゴリ機能があるせいか?)対応するソースは作成できない

  • 既存クラスから派生させて作った自作クラス:黒で表示される。派生クラスの作成ができる。アウトレットもアクションも追加可能。対応するソースを作成できる

  • First Responder:黒で表示される。派生クラスの作成はできない。アウトレットは追加できない。アクションは追加できる。対応するソースは作成できない

ともかく、First Responderにアクションを追加してメッセージを送ると、他のnibファイルにあるそのメッセージを処理できるオブジェクトがレスポンダチェーンに載ってれば、正しく処理される。
載ってなければ処理されない(例えばメニューなら自動的に不活性になる)

ただ、First Responderに例えばshowMailboxesPanel:を追加しても、実際にどこかのクラスにshowMailboxesPanel:っていうメソッドが追加されるわけではないことに注意。
あくまで線を引っ張るためだけにアクションを追加するのね。
線を引っ張ることさえできたら、そこからObjective-Cの特徴である動的バインドが有効に働く。

この仕組みがない場合、NSBundle#loadNibNamed:とかでロードする際にポインタをつかんでおいて、半ばアーリーバインド的にメッセージ送信せざるを得ないんだけど、この仕組みがあればポインタをつかむ必要も、ポインタをつかむオブジェクト(デリゲートを兼ねたような奴)を作ったりする必要も全然ない。

投稿者 kabeya : 2003年12月28日 12:02