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

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

 〜 プロパティとプロトコル 〜


今回からは、 画像ファイルを読み込んで、そのパス名などをImageFileオブジェクトへと登録する仕組みをどうするのか順次考えて行きます。その前に、せっかくObjective-C 2.0を使うわけですから、以前に作成したImageFileクラスのインスタンス変数を「プロパティ」仕様に準拠させてみます。

インスタンス変数をプロパティ仕様に準拠させれば、自動的にコンパイル側でそれへのアクセッサメソッドを作成してくれます。ですから、アクセッサメソッドを別途ソースコードとして記述する必要はなくなります。また、C言語での構造体メンバーへのアクセスと同じように、「ドット演算子」を用いて、インスタンス変数へのアクセスが可能となります。例えば、こんな感じのソースコードが記述可能になるわけです。

ImageFile *image;

if( image=[[ImageFile alloc]init] )
{
  image.name=@"Untitled";
  image.type=@"JPEG File";
  image.kind=1;
  image.flag=0;
}

ドット演算子経由での変数アクセスを考慮し(今のままでは見づらいので)、 今回からインスタンス変数の先頭のアンダーラインは外すことにしました。この「先頭アンダーライン」ですが、荻原剛志さんの著書「Objective-C」には、先頭にアンダーラインを付けたインスタンス変数は避けるべきだと記載されています。Apple社が、Framework内のクラスなどで使用することはOKだが、一般開発者は使用しない方が良いと言う説もあります。ところが、Apple社のサンプルソースコードを見ると、何のためらいもなく使用している場合もあり(笑)Cocoa開発者を悩ませます。 さて、何が正解なのでしょうか?

ともあれ、ImageFileクラスで用意したインスタンス変数をプロパティ仕様に準拠させるには、クラスの雛形宣言(ImageFile.h)の中で@property宣言を使いインスタンス変数を列挙します。

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

@property(retain)NSString *path;
@property(retain)NSString *type;
@property(assign)CGRect srt;
@property(assign)unsigned int flag;
@property(assign)int kind;
@property(assign)int para;

@end

@propertyに続くカッコ内の(retain)は、プロパティの「属性」と呼ばれており、 retain、assign、copy、readonly、readwriteなど幾つかの種類があります。それらの詳しい機能については、以下のドキュメント(日本語訳)を参照してみてください。

「Objective-C 2.0プログラミング言語 」
http://developer.apple.com/jp/documentation/Cocoa/Conceptual/ObjectiveC/

例えば、属性がそれぞれretain、assign、copyであると、コンパイラが作成するアクセッサメソッドのタイプが次のように変わります。

// assignの場合

- (void)setKind:(int)newKind
{
  kind=newKind;
}

// retainの場合

- (void)setPath:(int)newPath
{
  if( path!=newPath )
  {
    [path release];
    path=[newPath retain];
  }
}

// copyの場合

- (void)setPath:(int)newPath
{
  if( path!=newPath )
  {
    [path release];
    path=[newPath copy];
  }
}

オブジェクトでないインスタンス変数(intやfloatや構造体など)であれば、 assign属性でOKだと言うのが理解できます。今回は、NSStringの場合にだけretain属性を使います。そして、もう一カ所だけプロパティに関係する定義を記述しておくべき箇所があります。それはクラスの実装部分( ImageFile.m)の方です。対象となるインスタンス変数について@synthesize宣言しておきます。実際の定義は、以下のようになります。

@implementation ImageFile

@synthesize path,type,srt,flag,kind,para;

// 各種メソッド表記

@end

プロパティ宣言についてはこれで完了です。ところで、 ImageFile.hの@interface行の最後を見てみると、そこに<NSCoding>という記述がありますが、これは、 ImageFileクラスがNSCodingプロトコルに準拠していることを意味しています。プロトコルとは、幾つかのメソッド(ひとつでも良い)のグループを指しており、そのメソッドをすべて実装したクラスのみが「そのプロトコルに準拠している」と呼ばれます。NSCodingプロトコルはNSObject.hに定義されており、以下の2つのメソッドを持つクラスがそれに準拠していることになります。当然、あるだけではなく、それらのメソッドがプロトコル定義の意図した動作をしないといけません。

@protocol NSCoding

- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;

@end

つまり、ImageFileクラスに、上記2つのメソッドを実装する必要があるわけです。 これらのメソッドはどういう働き(機能)をするのでしょうか?前回、NSDocumentメソッドをオーバライドして、以下の2つのメソッドを実装しました。dataOfType:error:メソッドは、ファイルにドキュメントを書き出すために、オブジェクトをバイナリデータ(NSData)に変換します。また、readFromData:ofType:error:メソッドは、ファイルから読み込んだバイナリーデータ(NSData)からオブジェクト(NSMutableArray)を再構築します。

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

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

両メソッドの実際の処理に注目してみましょう。NSMutableArrayをバイナリーデータ(NSData)に変換しているのは、NSArchiverクラスのarchivedDataWithRootObject:メソッドです。逆にバイナリーデータ(NSData)をNSMutableArrayに戻しているのは、NSUnarchiverクラスのunarchiveObjectWithData:メソッドです(こちらでもインスタンス変数の_listは先頭のアンダーラインを取りました)。この2つのアーカイブとアンアーカイブメソッドは、対象となるオブジェクトのクラスがNSCodingプロトコルに準拠していることが前提で使用されています。

data=[NSArchiver archivedDataWithRootObject:list] ) // バイナリーデータ作成

if( array=[NSUnarchiver unarchiveObjectWithData:data] ) // データからNSArrayを得る
   [list addObjectsFromArray:array]; // インスタンス変数に保存する

NSArray.hを見ると、NSMutableArray(NSArrayのサブクラス)は、NSCodingプロトコルに準拠していることが分かります。

@interface NSArray : NSObject <NSCopying, NSMutableCopying, NSCoding,NSFastEnumeration>

よって、NSMutableArrayに対しては両メソッドを使用しても大丈夫ですが、その配列に代入されているImageFileオブジェクトの方は、 NSCodingプロトコルに準拠しないと問題が生じます。つまり、ImageFileクラスにencodeWithCoder:とinitWithCoder:メソッドを実装する必要があり、具体的には、ImageFileオブジェクトからNSDataへ変換するアーカイブ処理と、NSDataからImageFileオブジェクトへ戻すアンアーカイブ処理の両方を実現しておくことになります。

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


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