SSブログ

Mac用USBデバイス-103 テストプロジェクトの続き [Mac用USBデバイス工作]

また先週から日にちがあいて忘れてしまった。土日も続ければいいんだけどうちに帰るとなぜかその気にならない。本当になんでだろう。

前回はNSTreeControllerを使ったCocoaバインディングのテストプロジェクトのUIとアプリのデリゲートの動作を考えた。今日はNSTreeControllerのサブクラスの具体的なコードと、NSTreeControllerのちょっと不思議な動作について。

5.3.9  NSTreeControllerのサブクラス

BranchedTreeControllerという名前のNSTreeControllerのサブクラスを作る。サブクラスはaddChild:を上書きするだけ。
@interface BranchedThreeController : NSTreeController
@end

@implementation BranchedThreeController
- (void)addChild:(id)sender
{
    id  childClass = [sender additionalObject];
    [super setObjectClass:childClass];
    [super addChild:sender];
}
@end
senderから前回デリゲートで設定したadditionalObjectを取り出す。これをNSObjectControllerのsetObjectClass:に設定したあと、NSTreeControllerのaddChild:メソッドを呼ぶ。前回にNSMenuItemのサブクラスを作ってRefConを保持すればいいとしたが、そのRefConを返すメソッドがadditionalObjectだとしている。

5.3.10  Tree Controllerの設定

あとはInterface Builder上での設定。
まず、MainMenu.xibに乗せたNSTreeControllerを今のBranchedTreeControllerと差し替える。それはInspectorパネルのIndentityでClassの名前を書き換えればいい。

また、TreeControllerのAttributesパネルで、
  1. 「Tree Controller」のKey Pathを「children」に(子要素を得るキーはchildrenである、ということ)
  2. 「Object Controller」のModeを「Class」に(Core Dataのエンティティではなく自分で用意したオブジェクトである、ということ)
  3. Class Nameを「Node」
にしておく(ただしClass Nameの設定は今回の場合、無視されることになる)。

5.3.11  setContent:メソッドとInterfaceBuilderでの設定の違い

さっきはプログラマティックにTreeControllerのcontentを設定したが、ここのBindingsパネルの「Content Object」でも設定できる(図-12)。
0524fig12.png
ただし今回のようにTreeControllerとcontentであるrootNodeを保持しているAppDelegateが同じxibファイルに乗っている場合(たいていそうだろう)、タイミングの問題が発生する。つまりxibファイルの上ではインスタンス化される順番がどうなるかわからないので、例えばrootNodeが作られる前にTreeControllerがインスタンス化されてしまうと初期化されていないrootNodeポインタをTreeControllerのcontentとして設定してしまう可能性がある。AppDelegateのawakeFromNibメソッドでrootNodeを作っているとすると(やはり普通はそうだろう)、場合によってはcontentとして設定されない、と言うことが起こる。

どうやらNSTreeControllerのcontentをInterface BuilderのBindingsパネルのController Contentで指定した場合、親クラスであるNSObjectControllerのinitWithContent:メソッドではなく、NSTreeControllerのawakeFromNibメソッドの中で設定されるようである。従ってrootNodeを作るのはAppDelegateのawakeFromNibではなく、initで作ればタイミングの問題はなくなる。

しかし今回なぜか動作が一致しなかった。

つまり、NSTreeControllerのcontentを
  1. プログラマティックにNSTreeControllerのsetContent:メソッドを呼ぶ
  2. Interface BuilderのBindingsパネルの「Controller Content」→「Content Object」でrootNodeを設定する
では、設定されるcontentの構造は同じなのに、子要素を加えるメソッドaddChild:が(ボタンのアクションとして)呼ばれると
(1)プログラマティックに設定した場合
addChild:メソッドから要素クラスのinitが呼ばれて親のchildren配列に追加される
(2)Interface Builder上で設定した場合
ではinitが呼ばれない。当然contentの構造に変化はない
となった。ただし、厳密に言えば要素クラスのinitはaddChild:メソッドの中で呼ばれるわけではなく、NSInvocationを介してNSRunLoopから呼ばれている。これは単にNSOutlineViewの上での表示効率の問題からこうなっていて本質的ではないと思われる。

5.3.12  NSTreeControllerのContentの保持のしかた

一般的なツリー構造とは、幹になるひとつのオブジェクトがあり、それが複数の子オブジェクトを持ち、さらに子オブジェクトが孫を、という構造のことを言う。この構造のことをここでは「単一ルート」のツリーと呼ぶことにする。NSTreeControllerはこの単一ルートのツリーと、トップレベルがすでに配列になっていて、それぞれが子供孫、を持っているさらに一般的なツリーも扱うことができるようになっている。この「複数ルート」(変な言葉だけど)のツリーはNSOutlineViewの表示方法に対応している。

NSTreeControllerはこの両方のツリーを同じ形式で保持しているらしい。つまりNSTreeControllerのcontentは配列で、単一ルートの場合はその要素がひとつだけとして扱っている。Interface BuilderのBindingsパネルの「Controller Content」→「Content Object」で単一ルートのオブジェクトを指定して、NSTreeController(のインスタンス)にcontentメソッドを投げると、指定したオブジェクトをひとつだけ持っているNSMutableArrayが返ってくるのでわかる。

扱いを一般化して統一する、という意味では正しいが、どうやらNSTreeControllerはこの区別は設定するときにだけ意味があって、そのあとはまったく区別していないようである。ルートが単一か複数かを指定するためのメソッドも、あるいはその設定を保持するようなそれらしいインスタンス変数もない。

さきほどプログラマティックに設定した場合と、Interface Builderで設定した場合で動作が違っていて、Interface Builderでは子要素の追加メソッドを呼んでも子供が作られない、と指摘した。
0524fig13.png
実は、図-13のようにInterface BuilderのBindingsパネルで「Controller Content」→「Content Object」ではなく、「Controller Content」→「Content Array」で、単一ルートのオブジェクトをNSMutableArrayに入れたもの(図ではrootArrayがNSMutableArray)を設定すると、contentの構造は全く同じなのに、プログラマティックに設定した場合と同じ動作に変わってしまう。

結局今回、しょうがないのでプログラマティックに設定することにしたが、なぜこういう動作をするのか理解できない。どなたかこの辺りの挙動に詳しい人が教えてくれるとありがたい。
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

献立05/24献立05/25 ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。