読者です 読者をやめる 読者になる 読者になる

sandbox

Scala, Android, Architecture, Management, Service Design あたりを主戦場としております

Objective-C 2.0 プログラミング言語を読む #4 プロパティ

出張三昧で時間が空いてしまった。。
iPhone の発売を心待ちにしつつ、勉強再開。

プロパティとは

オブジェクトのプロパティを宣言によって実装する方法。
また、アクセサメソッドを記述せずにオブジェクトのプロパティにアクセス出来る構文も用意されている。
プロパティには2つの側面があり、プロパティを宣言する為の構文要素と、アクセスする為の構文要素がある。

プロパティの目的

アクセサの記述が面倒なので、宣言で簡単に記述できるようにした。
例えば、

  • 宣言により、アクセサメソッドの動作方法を指定出来る。
  • 宣言で指定された仕様に基づき、コンパイラがアクセサメソッドを合成出来る。
  • 宣言されていないプロパティの使用の検出。
  • プロパティの実行時のインストロペクション。

プロパティの宣言

@property を使用してプロパティを宣言する。
宣言はメソッド宣言リスト内の任意の場所に置くことが出来、プロトコルやカテゴリ宣言のなかにも置くことが出来る。

@interface MyClass:NSObject 
{ 
  NSString*value; 
} 
//@property(attributes) type name と宣言する
@property(copy, readwrite) NSString *value;
@end 

@implementationMyClass 
@synthesize value; 
@end 

プロパティの実装ディレクティブ

@implementation ブロックでは、@synthesize と @dynamic を使用して、コンパイラの動作を指定出来る。

ディレクティブ 意味
@synthesize 関連するアクセサを合成する
@dynamic 実行時に自分自身でメソッド用意する(つまり合成しない)。デフォルト

こんな風に一度に宣言することも出来る。

//各プロパティのアクセサを合成し、age プロパティはインスタンス変数 yearsOld によって表すことを指定。
@synthesizefirstName, lastName, age = yearsOld;

プロパティ宣言属性

必要に応じて @property(attributes) の形式で、プロパティを属性で修飾することが出来る。

属性 意味
getter=gtterName
,setter=setterName
アクセサの名前を指定したい時に指定する。デフォルトではキー値コーディングの表記規約に従う。
readonly 読み取り専用であることを示す。ドット構文を使用して値を代入しようとするとコンパイル警告が発生する。
readwrite 読み書き可能であることを示す。デフォルト。
assign setter で単純代入を使用することを指定する。デフォルト。GC を使用していない環境では適切ではない為、コンパイラ警告が発生する。
retain 代入時に retain を呼び出すことを指定する。Objective-C オブジェクトに対してのみ有効。
copy 代入時にオブジェクトのコピーを使用することを指定する。NSCopying プロトコルを実装する必要がある。
nonatomic 合成されるアクセサが非アトミックになるように指定する。デフォルトでは全てアトミック。

assign, retain, copy でアクセサを合成した場合は、それぞれ下記の様な実装になる。

// assign 
property = newValue; 

// retain 
if (property != newValue) 
{ 
  [property release]; 
  property = [newValue retain]; 
}

// copy 
if (property != newValue) 
{ 
  [property release]; 
  property = [newValue copy]; 
} 

プロパティの再宣言

サブクラス、カテゴリ、プロトコルでプロパティで再宣言出来る。
その場合は、そのサブクラス、カテゴリ、プロトコル全体でプロパティの属性を繰り返す必要がある。
#これは同じ属性を宣言しとけってことか?

1つ例外として readonly と readwrite は、readonly で宣言したプロパティを、
サブクラス、カテゴリ、プロトコル で readwrite として再宣言出来る。

プロパティへのアクセス

ドット構文

Java などと同様にドット構文でアクセス出来る。

MyClass *myInstance = [[MyClassalloc] init]; 
myInstance.value = @"Newvalue"; 
NSLog(@"myInstancevalue:%@", myInstance.value); 

ただ、これはシンタックスシュガーでしかなく、実際はコンパイラによってアクセサメソッドの呼び出しに変換される。

MyClass *myInstance = [[MyClassalloc] init]; 
[myInstancesetValue:@"Newvalue"]; 
NSLog(@"myInstancevalue:%@", [myInstancevalue]); 

また自身のインスタンス変数にアクセスする場合は self を用いる。
self を指定しないと直接インスタンス変数にアクセスすることになる。

self.age = 10; //setter が呼びだされる
age = 10; //インスタンス変数へ直接アクセスされる
キー値コーティング

定義したプロパティにはキー値コーディングでもアクセス出来る。
キー値コーディングとは、 NSObject で定義されている valueForKey:, setValue:forKey: でアクセサメソッドを
呼ぶことが出来る仕組みである。
何が嬉しいのかというと、引数には文字列でプロパティ名を渡せる為、実行時に呼ぶアクセサを動的に変えることが出来る。
#これは Java とか JavaScript のリフレクションみたいだな。

@interface Person
{
  NSString *name;
  NSInteger age;
}

@property(copy) NSString *name;
@property NSInteger age;

@end

...

Person *person = [[Person alloc] init];
NSString *name = [person valueForKey:@"name"]; //person.name
[person setValue:20 forKey:@"age"] //person.age = 20;
プロパティのサブクラス化

readonly な属性をサブクラスで readwrite にするなどの様に、プロパティもオーバーライドすることができる。

プロパティのインストロペクション

class_getProperty(Class cls, const char *name) の様にプロパティを取得でき、
property_info(Property *property, const char **name, const char **type) でプロパティの情報を取得出来る。

他にも class_copyPropertyList や、 property_copyAttributeList 関数などもある。

ランタイムの相違

32bit 環境では、@interface 宣言ブロックにインスタンス変数を宣言しなければならない。
64bit 環境では、自動でインスタンス変数を合成してくれる。