● Carbon視点でCocoa探求(2008/05/20)

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

 〜 NSCodingプロトコルを実装する 〜


「しんぶんし 3」でドキュメントのロードやセーブを実現するには、 ImageFileクラスに対してもう一仕事する必要があることが分かりました。今回は、先んじてこの点を解決してから画像ファイル関連の話へ移りたいと思います。

おさらいとして、もう一度ImageFileクラスのインスタンス変数を見てみます。スーパークラスとして定義されているNSObjectのさらに右側の表記 <NSCodeing> が、このクラスがNSCodingプロトコルに準拠していることを示します。

@interface ImageFile : NSObject  <NSCoding>
{
  NSString    *path;
  NSString    *type;
  CGRect     srt;
  unsigned int   flag;
  int       kind;
  int        para;
}

NSCodingプロトコルに準拠させるために、ImageFileクラスにencodeWithCoder:とinitWithCoder:メソッドを実装してみます。具体的には、ImageFileオブジェクトからNSDataへ変換する(エンコード)アーカイブ処理と、NSDataからImageFileオブジェクトへ戻す(デコード)アンアーカイブ処理の両方を実現する必要があります。

ImageFileクラスのインスタンス変数を構造体のメンバーと見なせば、それを一つずつエンコードしていき、6つ全部実行すれば作業は終了となります。以下が、エンコード用に実装するencodeWithCoder:メソッドです。NSMutableArray(list)に保存されているImageFileオブジェクトが順次アーカイブ処理される時に、このメソッドが逐次呼び出されて実行されるわけです。

- (void)encodeWithCoder:(NSCoder *)coder
{
  [coder encodeObject:path];
  [coder encodeObject:type];
  [coder encodeValueOfObjCType:@encode(unsigned int) at:&flag];
  [coder encodeValueOfObjCType:@encode(CGRect) at:&srt];
  [coder encodeValueOfObjCType:@encode(int) at:&kind];
  [coder encodeValueOfObjCType:@encode(int) at:&para];
}

インスタンス変数の最初の2つはNSStringです。既存のCocoaオブジェクトについては、まずはそのクラスが定義されているヘッダファイル(NSString.h)を見てみます。すると、クラス定義の@interface行に対応しているプロトコル名が列記されていることが分かります。

@interface NSString : NSObject <NSCopying, NSMutableCopying, NSCoding>

つまり、NSStringの場合には、NSCopying、NSMutableCopying、NSCodingの3つのプロトコルが実装されています。エンコード対象のクラスにNSCodingプロトコルが実装されていれば、NSCoderクラスのencodeObject:メソッドを使えます。これが、対象オブジェクトのencodeWithCoder:メソッドを呼び出すことで、処理が芋ズル式に進みます。NSStringのpathとtypeに対する処理はこれでOKです。

続いて、整数(int)、浮動小数点(float)、構造体などの非オブジェクトのエンコードとなります。こちらの実現には、encodeValueOfObjCType:at:メソッドを使います。メッセージとして渡している@encode()は、エンコードされた内容(種類)をC文字列で返すコンパイラ指示子です。例えば、@encode(int)なら"i"に、@encode(flaot)なら "f"となります。構造体の@encode(CGRect)の場合には、"{?=ffff}"(4つの浮動小数点)というC文字列となります。@encodeには、sizeof()演算子の引数として使用できる任意の型を指定できるわけです。@encodeについての詳しい内容については、以下のドキュメントを参照してみてください。

「Objective-C 2.0プログラミング言語」127ページ

http://developer.apple.com/jp/documentation/Cocoa/Conceptual/ObjectiveC/

続いて、ImageFileクラスのデコード用に実装するinitWithCoder:メソッドです。

- (id)initWithCoder:(NSCoder *)coder
{
  if( self=[super init] )
  {
    self.path=[coder decodeObject];
    self.type=[coder decodeObject];
    [coder decodeValueOfObjCType:@encode(unsigned int) at:&flag];
    [coder decodeValueOfObjCType:@encode(CGRect) at:&srt];
    [coder decodeValueOfObjCType:@encode(int) at:&kind];
    [coder decodeValueOfObjCType:@encode(int) at:&para];
  }
  return self;
}

こちらも、NSCodingプロトコル準拠のオブジェクトについては、NSCoderクラスのdecodeObject:メソッドを使います。そうでない整数等はdecodeValueOfObjCType:at:メソッドを使います。この時、デコード順序がエンコード順序と異なると処理は正しく行われません。デコード処理はエンコード時と同じ順序で列記してください。
ところで、ImageFileクラスのインスタンス変数はプロパティ宣言されています。プロパティの「属性」が(retain)であるpathとtypeは、返り値をself.pathとself.typeに代入しています。これは、コンパイラが用意したアクセッサメソッド経由で値を代入したのと同じ意味を持ちます。ここで、返り値をpathやtypeにダイレクトに代入してしまうと、得られたNSStringがautoreleaseされますので、ある時点でエラーが発生してしまいます。注意しましょう。

また、ImageFileクラスのスーパークラスはNSObjectなので、先んじて以下の様な処理を実行していますが...

if( self=[super init] )

もし、このスーパークラスがNSObjectではなくNSCodingプロトコルに準拠した別のクラスであれば、上記の部分は以下のように書き換える必要があります。こちらも注意しましょう。

if( self=[super initWithCoder:coder] )

さて、これでようやくImageFileオブジェクトのエンコード&デコード処理が実装されました。これにより、オブジェクトデータのドキュメントファイルへの書き出しと読み込み(アーカイブ処理)が無事可能になったわけです。続いて「しんぶんし 3」のドキュメントファイルや画像ファイルの取り扱いを考えてみます。

・ドキュメントファイルの場合

(1)ファイルメニューの「開く...」でファイルを読み込み内容をウィンドウに表示
(2)ファイルメニューの「保存」で内容をファイルへ書き出す
(3)ファイルメニューの「別名で保存...」で名称を変えてファイルへ書き出す
(4)ファイルメニューの「最後に保存した状態に戻す」で編集をキャンセルする
(5)未保存の編集済みウィンドウを閉じた時には「保存」処理を実行する
(6)終了時に未保存の編集済みウィンドウに対して「保存」処理を実行する
(7)アプリ・アイコンへのドロップで起動と内容を表示したウィンドウを開く
(8)新規や登録済みウィンドウにドロップすることでその内容を追加登録する
(9)ドキュメントのエイリアスファイルでも上記同等の処理を行えるようにする

・画像ファイルの場合

(1)登録ボタンでウィンドウに画像を追加登録
(2)ドラッグ&ドロップでウィンドウに画像を追加登録
(3)アプリ・アイコンへのドロップで新規ウィンドウを開き画像を登録
(4)画像ファイルが含まれたフォルダについても上記と同等の処理を行う
(5)画像ファイルのエイリアスファイルについても上記と同等の処理を行う

ドキュメントファイルの(1)から(7)までの処理は、Info.plistに対象ドキュメントのタイプや拡張子を正しく記載しておけば、NSDocumentクラスが自動で処理してくれますが、(8)や(9)の一部、画像ファイルの( 1)から(5)までの処理については自分で実装する必要があるでしょう(多分)。Carbonアプリケーションであれば、こうしたファイル処理は、すべて自分自身で実装(処理用のコードを記述する)することになりますが、「Cocoa Document-based Application」テンプレートを利用すると随分と楽ができます(笑)。

ただし、Cocoaアプリケーションの場合でも、NSDocumentの力を借りなければ、多くのファイル処理を自分で実装する必要があります。その良いサンプルは、/Developer/Examples/AppKit/に含まれる「TextEdit」です。このサンプルソースコードは、Mac OS X付属アプリ「テキストエディット」のXcodeプロジェクトとなります。Cocoaのドキュメント処理に関してさらに突っ込んで勉強したい方は、ぜひ参照してみてください。

今回は、ドキュメントや画像ファイルについて実装すべき処理を列挙してみました。ただし、先んじてウィンドウに画像を表示する仕組みを作らないと、こうした処理の動作確認は困難です。次回は、登録された画像ファイルの内容をウィンドウに表示する方法について色々と考察してみたいと思います。


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