● Carbon視点でCocoa探求(2008/04/22)

  このニュースは、MOSAの会員にのみ配布されているデベロッパー向けの
  デジタルマガジンMOSADeNのに掲載された記事です。ほぼ一ヶ月遅れで
  ここに掲載されて行きます

 〜 NSDocumentメソッドのオーバライド 〜


今回は、 MyDocument.mでオーバライドすべきNSDocumentメソッドの内容を解説します。こうしたメソッド(候補)は、「Cocoa Document-based Application」プロジェクトを新規作成した時点で、最初から5つだけ記述されています。

(1)- (id)init

initは、MyDocumentの初期化メソッドです。MyDocumentのインスタンスが作成される時に何か独自の初期化が必要であれば、このメソッドをオーバライドします。本アプリケーションでは、画像ファイルのパス名などを「ImegeFile」オブジェクトに保存することになります。その配列を保管しているNSMutableArrayは、MyDocumentのインスタンス変数(_list)ですので、最初にそれを作成して初期化する必要があります。

@interface MyDocument : NSDocument
{
 NSMutableArray *_list;
}
@end

後々、MyDocumentのインスタンス変数の種類は増えるかもしれませんが、とりあえず今回のinitは以下のようになります。

- (id)init
{
 if( self=[super init] )
   _list=[[NSMutableArray alloc] init]; // NSMutableArrayの作成と初期化
 return self;
}

独自の初期化をする前に、スーパークラス(今回はNSDocument)の初期化を実行することを忘れないでください。

(2)- (NSString *)windowNibName

windowNibNameメソッドでは、ドキュメント表示用のウィンドウ・オブジェクトが保存されているnibファイルの名称を返します。今回のNibファイルは、MyDocument.nibですので、このメソッドは以下のようになります。これにより、NSDocumentがnibファイルを読み込み適切なウィンドウを作成してくれます。

- (NSString *)windowNibName
{
 return @"MyDocument";
}

ドキュメントの内容をひとつのウィンドウに表示するだけなら上記処理で問題無いのですが、アプリケーションによっては、ドキュメントの内容を複数のウィンドウに表示したい時があります。この場合には、上記メソッドのオーバライドではなく、代わりに以下のmakeWindowControllersメソッドをオーバライドします。

(void)makeWindowControllers;

通常では、このメソッドがwindowNibNameメソッドを呼んでいるわけで。しかし、直接こちらを用いる場合には、アバウトを表示する時に利用したNSWindowControllerクラスのinitWithWindowNibName:メソッドなどを使い、nibファイルからウィンドウを呼び出し、そのWindowControllerをaddWindowController:メソッドでNSDocumentオブジェクトに登録する必要があります。よって、そこでの処理は若干複雑となります。

(3)- (void)windowControllerDidLoadNib:(NSWindowController *) aController

windowControllerDidLoadNib:メソッドは、ドキュメント内容を表示するウィンドウが作成された直後に呼ばれます。ここでは、ウィンドウ、もしくはWindowControllerに必要とされる独自初期化(読み込んだドキュメントのデータをビューに表示するなど)を実行することになります。こちらも、先んじてスーパークラス側のメソッドを呼び出すことを忘れないでください。

- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
 [super windowControllerDidLoadNib:aController];
 [[aController window] center]; // Windowをスクリーンの中心に表示する
}

今回は、ウィンドウに関する初期化処理は何もありませんが(まだデータ内容の表示については何も準備されていないので)、例題としてウィンドウを強制的にスクリーンの中心に移動させる処理を追加してみました。

(4)- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError

次のdataOfType:error:メソッドには、ドキュメント内容をファイルに書き出すために、それをバイナリデータ(NSData)に変換する処理を実装します。新規ドキュメントであれば、このメソッドが呼ばれる前にファイル名入力ダイアログが表示され、ユーザが保存場所と名称を入力していることになります。typeNameには、書き出すべきファイルタイプが入ってきますが、今回は対象ドキュメントが一種類だけなので(info.plistの登録されている)、何かを判断するためにそれをチェックする必要はありません。

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
 NSData  *data=nil;

 if( data=[NSArchiver archivedDataWithRootObject:_list] )  //バイナリーデータ作成
  return data;
 else
 {
  if( outError!= NULL )
   *outError=[NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL];
 }
 return data;
}

バイナリーデータは、NSArchiverのメソッドであるarchivedDataWithRootObject:に対象となるNSMutableArrayを渡して作成しています。このメソッドから返されたバイナリーデータを、NSDocumentが責任をもってファイルへと保存するわけです。

古いCocoaの解説書の中には、dataOfType:error:の代わりにdataRepresentationOfType:メソッドが紹介されている場合があります。 それを含め、 Mac OS X 10.4から、いくつかのNSDocumentメソッドがDEPRECATED宣言(推奨しない)されました。ただし、アプリケーションのターゲット環境にMac OS X 10.3以前が含まれる場合には(今回は違う) dataOfType: でなくdataRepresentationOfType:の方をオーバライドする必要がありますので、注意してください。

(5)- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError

最後の、readFromData:ofType:error:メソッドはdataOfType:error:の逆で、ファイルから読み込まれたバイナリーデータ(NSData)からドキュメント内容(NSMutableArray)を構築します。 つまり、このメソッドが呼ばれる前にはファイル選択ダイアログが表示され、ユーザが対象ファイルを選び、NSDocumentがそのファイルのバイナリーデータを読み込んでくれているわけです。

- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
 BOOL      ret=NO;
 NSMutableArray *array;

 if( array=[NSUnarchiver unarchiveObjectWithData:data] )  // データからArrayを得る
 {
  [_list addObjectsFromArray:array];  // インスタンス変数に保存する
  ret=YES; // 読み込み成功
 }
 else
 {
  if( outError!= NULL )
   *outError=[NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL];
 }
 return ret;
}

バイナリーデータは、NSUnarchiverのメソッドであるunarchiveObjectWithData:を使いNSMutableArrayに変換します。その後、addObjectsFromArray:でインスタンス変数の_listに追加します。copyでなくaddなのは、新規だけでなく、画像ファイルの登録追加処理にもこのメソッドを使いたいためです。またバイナリーデータ読み込みの方も、ターゲット環境が10.3以前の場合は、readFromData:ofType:error:の代わりにloadDataRepresentation:ofType:メソッドをオーバライドする必要がありますので、注意してください。

ファイルのセーブとロードについては、さらに追加処理が必要となるかもしれません。ここまで準備を進めれば、次は実際に画像ファイルを登録してウィンドウに表示する仕組みを用意する番でしょう。次回は、 複数の画像ファイルを読み込んで、そのパス名などをImageFileオブジェクトへと登録する仕組みをどうするのか考えてみます。


copyright 2008 Ottimo, Inc. All rights reserved
無断転載・引用禁止