Doc-Based App + Cocoaバインディングでのもう一つの
モデルデータの持ち方(っていうかこっちが本命?)


昨日作成した、2つのArray型データを持ち、ドキュメントベースかつCocoaバインディングを使用したアプリでのデータファイルの保存と読み込みをテーマにした「ドキュメントベースアプリにおける2つのArrayのアーカイブ実験」のサンプルでは以下のようなデータの持ち方をしていました(分かりやすいように実際のサンプルよりは図を簡略化しています)。
モデルデータはNSArrayControllerが握っており、保存/読み込み時にはドキュメントクラスがNSArrayControllerへのOutlet経由でモデルデータにアクセスするようになっていました。

その後、ドキュメントベースかつCocoaバインディングのアプリでドキュメントクラスのdirtyフラグをセットする手段を模索する中で、以下のようなデータの持ち方もできることがわかりました。

つまり、データを保持しているのはあくまでドキュメント側であり、コントローラからはCocoaバインディングのメカニズムで接続し、ドキュメント内のデータにアクセスするというものです。
考えてみればこちらのやり方の方が自然ですね。

1.サンプルの概要

マンネリですが(^_^;)以下のような画面を持つドキュメントベースのアプリです。



2.ドキュメントクラスへのモデルデータ保持用インスタンス変数追加

デフォルトで作成されるMyDocumentクラスのインターフェースファイルにNSArrayControllerが扱うNSMutableArray型の以下のインスタンス変数を追加します。

@interface MyDocument : NSDocument
{
    // モデルデータはNSArrayController内部ではなくドキュメント側に持たせるようにする
    NSMutableArray        *_bookList;                // 書籍情報リスト
}
@end


3.NSArrayControllerからドキュメントクラスのインスタンス変数へのバインディング

NSArrayControllerからドキュメントクラスのインスタンス変数へのバインディングを行うには、まず、Interface BuilderでMyDocument.nibを開きます。
次にnibパネル上のNSArrayControllerのインスタンスを選択し、infoパネル(インスペクタ)を開き「Bindings」を選択します。
バインディングカテゴリ「Controller Content」の中のcontentArrayをクリックして開き、以下の設定を行います。

「Bind」ボタンにチェックを入れてこのボタンがコントローラとバインドされることを示します。(明にチェックしなくても「Binding to:」の設定を行う事で自動的にチェックされます)
「Binding to:」にMyDocumentのインスタンスである「File's Owner」を選択します。
「Controller Key:」は入力不可なので何もしません。
「Model Key Path:」には先程MyDocumentクラスに追加したNSMutableArray型インスタンス変数の"_"をとった"bookList"を指定します("_"はとらなくてもいいかもしれない)。
これでCocoaバインディングのコントローラがここに指定した文字列からドキュメントクラス内のインスタンス変数を自動的に見つけ出してアクセスしてくれるようになります。

カテゴリ「Controller Content」の中には以下の3つの項目がありますが、今回はNSArrayControllerが対象なのでcontentArrayに対してバインディングの設定を行っています。

 ・contentArray
 ・contentArrayForMultipleSelection
 ・contentObject

4.ドキュメントクラスのインプリメント

自動生成されるMyDocumentクラスのインプリメントファイルのスケルトンの以下のメソッドに初期化、保存、読み込み等で必要な以下の処理を組み込みます。

// 初期化
- (id)init
{
    self = [super init];
    if (self) {
    
        // Add your subclass-specific initialization here.
        // If an error occurs here, send a [self release] message and return nil.

        // 書籍情報リストの初期設定
        _bookList = [[NSMutableArray array] retain]; 
    }
    return self;
}


// 後始末
- (void)dealloc
{
    if ( _bookList != nil ) {
        [_bookList release];
    }
    [super dealloc];
}


// データ保存時の処理
- (NSData *)dataRepresentationOfType:(NSString *)aType
{
    // Insert code here to write your document from the given data.  You can also choose to override -fileWrapperRepresentationOfType: or -writeToFile:ofType: instead.

    // インスタンス変数に保持している書籍リストをアーカイブ化
    return [NSKeyedArchiver archivedDataWithRootObject:_bookList];
}


// データ読み込み時の処理
- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)aType
{
    // Insert code here to read your document from the given data.  You can also choose to override -loadFileWrapperRepresentation:ofType: or -readFromFile:ofType: instead.

    // NSDataから書籍リストを復元
    _bookList = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    return YES;
}

前のデータの保持方法ではloadDataRepresentation:でデータロード後、windowControllerDidLoadNib:でNSArrayControllerにcontentの設定をしてやらなければならないという、ちょっと美しくない作りをせざるを得なかったのですが、この方式ではloadDataRepresentation:でバインディングされているインスタンス変数へ設定するだけで自動的にコントローラへも反映されるようになりますので、よりシンプルで美しい作りとなりました。

6.Finish!!

以上で作成作業は完了ですのでビルド&実行してみてください。
なお、現在のコードではまだドキュメントのdirtyフラグを設定する処理がわからなくて組み込んでないので、明に"Save"メニューで保存しないとウィンドウのクローズでは保存確認されずに勝手にクローズされてしまいますのでご注意ください。

サンプルプログラムのダウンロード(28KB)

【作成・確認環境】
Mac OS X v10.3.4
Xcode v1.2


一覧に戻る