複数バンドルを使うアプリケーションの構築
複数の読込み可能バンドルとしてアプリケーションを作るのには幾つかの理由があり、またそれを実現するのにも幾つかの方法があります。 本章では、モジュール式の動的読み込みが可能なコンポーネントでアプリケーションを構築するためのCocoaの使い方を解説します。
複数バンドルを使うアプリケーションの設計
複数の読込み可能バンドルによるCocoaアプリケーションの設計は、開発者に幾つかの利点をもたらします:
- 必要になるまでコードの読み込みを遅らせる事が出来る。
- 独立して開発、コンパイルが行える単位にアプリケーションをモジュール化出来る。
- アプリケーションに拡張性を持たせることが出来る。
これらのゴールは2つの階層レベルで見ることが出来ます。1つは大規模アプリケーション構造、もう1つは小規模な機能のレイヤーです:
- 遅延コード読み込みとモジュラー化を実現するために、環境設定ウィンドウやドキュメントタイプといった大規模アプリケーションの構成要素に複数の読込み可能バンドルを使用する事が出来ます。
- 拡張性を実現するために、プラグインアーキテクチャを設計出来ます。この方法は、アプリケーションのソースコードにアクセスすることなく機能を容易に追加する事を可能にします。
遅延読み込みとモジュール化のより詳しい情報は、本章の“遅延バンドル読み込み”と“読込み可能バンドルによるモジュール化”をご覧下さい。
プラグインアーキテクチャの設計と実装についての情報は“プラグインアーキテクチャ”と“プラグインアーキテクチャの作成”をご覧下さい。
遅延バンドル読込み
アプリケーションで、潜在的に多くの場所の読込み可能バンドルに含まれるクラスのインスタンスにアクセスする必要があるかもしれません。
そのようなオブジェクトを直接指すポインタを使う場合、使用前にポインタがオブジェクトで初期化されているかどうか確認する必要があります。
これはオブジェクトを使用毎に余計な手順を追加し、nil
ポインタにメッセージを送るといったような見つけ難い潜在的なミスを誘います。
バンドルから読み込まれた全てのインスタンスについてアクセサメソッドを追加することにより、こうした複雑性を避けることが出来ます。 アクセサメソッドは開発者のために必要な全てのチェックを行います。オブジェクトが初期化済みならオブジェクトを返し、そうでなければ、バンドルを読み込み、オブジェクトを初期化し、そしてオブジェクトを返します。 アプリケーションの残りのコードは、ただアクセサメソッド経由でオブジェクトを使用するだけでよく、オブジェクトが初期化済みかどうかを心配する必要はありません。
リスト1は、バンドルの主要クラスを遅延初期化するアクセサメソッドの例です。 説明はリストの後に続きます。
リスト1 読込み可能バンドルのクラスから初期化されたオブジェクトのアクセサメソッド
- (id)bundleObject { if(!_bundleObject) // 1 { NSString *bundlePath = [[[NSBundle mainBundle] builtInPlugInsPath] stringByAppendingPathComponent:@"MyBundle.bundle"]; // 2 NSBundle *bundle = [NSBundle bundleWithPath:bundlePath]; // 3 if(bundle) { Class principalClass = [bundle principalClass]; // 4 if(principalClass) { _bundleObject = [[principalClass alloc] init]; // 5 } } } return _bundleObject; // 6 }
コードが行っていること:
- インスタンス変数
_bundleObject
が有効か確認します。有効ならif
文の中身はスキップされます。 - バンドルのパスを見つけます。この例ではパスをハードコードしていますが、殆どのアプリケーションにおいてより洗練された方法で指定されます。
- バンドルのパスに対応するNSBundleオブジェクトを取得します。このメッセージは、バンドルに対応するNSBundleが一つもなければ暗黙的にNSBundleを生成します。
- そのNSBundleオブジェクトが適切であれば、バンドルの主要クラスを取得します。バンドルの実行可能コードが読み込まれていなければ、このメッセージはコードを遅延読み込みします。
- 主要クラスが存在すれば、
_bundleObject
インスタンス変数の確保と初期化を行います。 - 最後に、
_bundleObject
を返します。読み込み処理に失敗すると、_bundleObject
の値はnilのままです。
読込み可能バンドルによるモジュール化
読込み可能バンドルでアプリケーションをモジュール化する多くの理由があります。 アプリケーション全体を再コンパイルすることなく、別のアプリケーションコンポーネントで再構築出来るように、開発作業を分割したいかもしれません。 別のアプリケーションコンポーネントを遅延読み込みしたいかもしれません。 あるいは、その両方を行いたいかもしれません。
これら目的を達成するため、恐らく、下記のうちの1つもしくは両方を行いたいと思うでしょう:
- 開発作業の容易な分割のためにコードの構成要素を別のバンドルに分ける。
- メインウィンドウと関連するコントローラのコードを、それぞれの読込み可能バンドルに分ける。
次の2つの節でこれら作業の実現方法を解説します。
コードコンポーネントの構築
コードコンポーネントの読込み可能バンドルを作るには、新しくXcodeプロジェクトを作成するか、既存のプロジェクトにターゲットを追加します。 目的が開発作業の分割の場合、恐らく、個々のバンドルごとにプロジェクトを分けたいと思うでしょう。 単にコードの遅延読み込みだけであれば、アプリケーションプロジェクトに新規ターゲットを追加するだけで良いでしょう。
一般的に、工程は以下の通りです:
- コードコンポーネントを含むバンドルの作成
- バンドルをアプリケーションバンドルの“plug-in”ディレクトリへコピー
- バンドルからコードを読込み使用するためにアプリケーションを記述
続きの項では、これらタスクの達成に必要となる具体的な手順を解説します。
バンドルの作成
読込み可能バンドルを作成するには、初めにバンドルが何のコードを持つのか決定し、そしてどのクラスを主要クラス─つまりエントリーポイントにするのかを決めます。 いったんそれが決まれば、バンドルのプロジェクトないしターゲットを構築する事が出来ます。
読込み可能バンドルのXcodeプロジェクトの作成手順は、“読込み可能バンドルの作成”で解説しています。
既存プロジェクトに新規ターゲットを加えるのも、同様の手順に従います。 しかし、新規プロジェクト作成の代わりに、以下の手順で新規ターゲットを既存プロジェクトに追加します:
- Xcodeでアプリケーションのプロジェクトを開きます。
- プロジェクトメニューの「新規ターゲット…」を選択します。
- Choose Bundle for the target type and click Next.
- Give the target a name in the Target Name field.
- Ensure that your project is selected in the Add To Project pop-up menu and click Finish.
- In the project window, make sure the Files tab is selected.
- Choose your target from the target pop-up menu.
- Click the checkboxes next to all the files you want included in this bundle. Make sure you include Cocoa.framework in the Frameworks > Linked Frameworks group. Be sure not to include implementations that appear in your application.
- Modify settings for the bundle target as described in “Modifying Target Settings.”
アプリケーションバンドルに読込み可能バンドルをコピーする
アプリケーションバンドルに読込み可能バンドルをコピーするには、アプリケーションターゲットにバンドルコピーのフェーズを追加し、読込み可能バンドルをアプリケーションバンドルの“plug-ins”ディレクトリにコピーします。
コピービルドフェイズの設定は、以下の手順を行います:
- Add the built bundle to your project in the Files tab.
- Click the Targets tab, and view the application target.
- Under Build Phases in the target pane, choose the last build phase (usually Frameworks & Libraries). This tells Xcode where to put the next build phase.
- Choose New Build Phase > New Copy Files Build Phase from the Project menu.
- In the Copy Files pane, choose Plug-ins from the pop-up menu labeled “Where:”.
- Click the Files tab in the main project pane.
- Drag the bundle from the list of project files to the box labeled “Files:” in the Copy Files section of the target pane.
これで、アプリケーションビルド時に、コードから容易にアクセス可能なアプリケーションバンドルのPlugIns
ディレクトリに、バンドルがコピーされます。
この追加コンポーネント用コピーフェイズに、追加バンドルを追記する事が出来ます。
バンドルコードの読み込み
アプリケーションのPlugIns
ディレクトリからバンドルを読み込む汎用的な工程については、“バンドルの読み込み”でも解説しています。
大規模アプリケーションの構成要素として多数の小さなバンドルを用いるアプリケーションにおいて、コードにアクセスする最も簡単な方法は遅延アクセサメソッドを通す事です。 バンドルからオブジェクトを遅延生成する方法は“遅延バンドル読込み”で解説します。
ウィンドウとウィンドウコントローラの構築
多くのアプリケーションにおいて、ウィンドウはそのウィンドウを管理するためのコード、典型的にNSWindowControllerのサブクラスと関連付けられます。 NSWindowControllerはウィンドウのnibファイルを遅延読み込みするための内蔵機能を提供します。 一歩進んで、そのNSWindowsControllerサブクラスのコードを遅延ロードし、続いてそれがnibファイルを読み込むように出来るでしょう。
この場合、そのnibファイルは、ウィンドウコントローラクラスと関連付けられるコードを持つ読込み可能バンドルの中に納められます。 アプリケーションは、必要になった時に初めてウィンドウコントローラのコードだけを読み込むことが出来、続いてそのウィンドウコントローラが必要になった時にウィンドウのnibファイルだけを読み込むことが出来ます。
このテクニックは、各プラグインがウィンドウと関連付けられるようなプラグインアーキテクチャにおいて、特に有用です。 例えば、グラフィックスアプリケーションのそれぞれのフィルタプラグインは、フィルタ設定調整のための関連するウィンドウを持っているかもしれません。 これは、巨大なアプリケーションコンポーネントが異なるウィンドウに相当するという形で、静的な状況へも同様に適用する事が出来ます。 例えば、回路シミュレーションアプリケーションの回路レイアウトウィンドウとグラフウィンドウです。
ウィンドウと関連コードのバンドルの構築手順は、これら2つのステップからなります:
- ウィンドウとウィンドウコントローラコードを含むバンドルの作成
- バンドル内のウィンドウコントローラを読み込み、利用するためのアプリケーションコードの記述
以下の節では、これら手順の詳細を解説します。
ウィンドウコントローラバンドルの作成
ウィンドウと関連するウィンドウコントローラのバンドルの構築は、本質的に“バンドルの読込み”で解説した事と同じです。 追加で、ウィンドウとそのウィンドウに関連付けられるウィンドウコントローラを持つバンドルプロジェクトに、nibファイルを含める必要があります。
新規Cocoaバンドルプロジェクトを作成したら、nibファイルを作成しプロジェクトに追加します:
- バンドルプロジェクトがXcodeで開かれているか確認する
- Interface Builderを起動する
- FileメニューからNew…を選択する
- Starting PointウィンドウのCocoaグループからEmptyを選択する
- FileメニューからSave As…を選択する
- バンドルプロジェクトディレクトリ内の言語
.lproj/
を開く。言語はnibファイルの言語コードで、例えばEnglish
やen
など。 - Saveをクリック
- In the Add File sheet, select the bundle target under Add to Targets, and click Add.
続いて、ウィンドウコントローラクラスを作成します:
- NSWindowControllerのサブクラスを作成する
- そのクラスに追加したいアウトレットとアクションを追加する
- Xcodeプロジェクトにクラスのソースコードを作成する
- Set the Custom Class for the File’s Owner proxy to your subclass.
ウィンドウ自身を作成し、全てと接続します:
- Add a new window object from the Cocoa-Windows palette and construct it with user interface elements.
- File's Ownerの
window
アウトレットを先ほど作ったウィンドウと接続し、コントローラと他のユーザーインタフェース部品との間で必要な接続を追加する。
最後に、Xcodeでウィンドウコントローラクラスのコードを書きます。 ある必須メソッドがNSWindowControllerに使用するnibファイルを教えます:
- (NSString *)windowNibName { // MyWindowControllerはあなたのウィンドウ名に置き換えて下さい return @"MyWindowController"; }
アプリケーションコードの記述
他のコードコンポーネント同様、遅延アクセサメソッドを通してウィンドウコントローラのバンドルにアクセスするのが最適です。 リスト2は、このようなメソッドの実装例を示します。
リスト2 バンドル内のウィンドウコントローラへのアクセサメソッド
- (NSWindowController *)bundledWindowController { // _bundledWindowControllerはid型またはNSWindowController *型の // プライベートインスタンス変数と仮定する。 if(!_bundledWindowController) { NSString *bundlePath = [[[NSBundle mainBundle] builtInPlugInsPath] stringByAppendingPathComponent:@"MyBundle.bundle"]; NSBundle *windowBundle = [NSBundle bundleWithPath:bundlePath]; if(windowBundle) { Class windowControllerClass = [windowBundle principalClass]; if(windowControllerClass) { _bundledWindowController = [[windowControllerClass alloc] init]; } } } return _bundledWindowController; }
アプリケーションコードの他の部分からは、アクセサメソッドを通じてそのオブジェクトを参照します。 例えば、この1行のコードはウィンドウを表示します:
[[self bundledWindowController] showWindow:self];
このサンプルには潜在的に2つの遅延読み込み操作が含まれています。
1つ目は、pricipalClass
メッセージが送信された時に、ウィンドウコントローラのコードが読み込まれます。
2つ目は、showWindow:
メソッドが送信された時に、ウィンドウのnibファイルが読み込まれます。