プロパティの話も4回目になった。今回は、プロパティの属性の話をしよう。実際にプロパティを使ったプログラミングをしていると、適切な属性の設定がとても大切だということに気づくだろう。

属性の文法

属性とは、プロパティを修飾するものになる。プロパティを設定したコードをコンパイルすると、それに合わせたアクセッサメソッドを作ってくれるのだが、このメソッドの種類や動作について、いくつかの細かい設定を可能にするのが属性だ。

文法としては、@property指示子の後に、括弧を付けて指定することになる。複数の属性を指定するときは、カンマで区切る。たとえば、次のコードではassignとreadwriteという2つの属性を指定している。

@property (assign, readwrite) NSString* value;

どんな属性が定義されているのか、詳しく紹介しよう。

アクセサの名前と種類

プロパティは、基本的にはドット演算子でアクセスする。だが、このアクセスは、結果的には対応するアクセサメソッドの呼び出しとなる。したがって、直接メソッド呼び出しの形で記述しても、結果は同じになる。

さて、このアクセサメソッドはコンパイラが自動的に作ってくれるのだが、その名前はどうなるのだろう? これは、キー値コーディングのルールに則って付けられることになる。キー値コーディングは、Mac OS X 10.3(Panther)の時代にCocoaバインディングの一部として追加されたものだが、いまやCocoaのあらゆるところで使われている(キー値コーディングについては、『TigerのCocoaにみるMVCの完成』を参照してほしい)。

この自動的に付けられる名前以外に、自分で名前を指定することもできる。そのために使われるのが、属性のgetterおよびsetterだ。getterは読み込み、setterは書き込みのためのアクセサの名前を指定する。次のように使う。

@property (getter=isEnabled, setter=makeEnable:) BOOL enabled;

getterメソッドは、引数はなしで、プロパティの値を返すもの。setterメソッドは、プロパティの型の引数を1つ取り、返り値はvoid型になるものになる。注意してほしいのは、setterは引数を取るので、最後にコロンを付けておかなくてはいけないことだ。Objective-Cでは、引数は必ずコロンの後に来る。このコロンを忘れると、コンパイルが通らない。

プロパティはデフォルトでは読み書き可能だが、読み込み専用にしたい場合もあるだろう。そんなときは、readonly属性を使う。これを指定したプロパティは読み込み専用となり、ドット演算子で書き込もうとすると、コンパイルがエラーとなる。無理矢理メソッド呼び出しで書き込もうとしても、対応するアクセッサが作られないため、実行時にエラーとなる。

プロパティが読み書き可能であることを明示したい場合は、readwrite属性を使うことができる。

@property (readwrite) NSString* fullName;
@property (readonly) NSString* firstName;

設定されたオブジェクトの保持

プロパティの型がオブジェクトで、それが書き込み可能な場合、それをどうやって保持するかが問題になってくる。何が問題になるかは、ガベージコレクションを有効にしているかどうかで変わってくる。

まず、ガベージコレクションを無効にしている場合から説明しよう。このときは、オブジェクトのオーナーシップをどうすべきか、気にしなくてはいけない。つまり、retainを呼んでそのオブジェクトを保持するのか、または単に参照を持つだけか、ということだ。

この挙動を、プロパティの属性で指定することができる。retainを呼ぶときはretain属性、単に参照を持つだけのときはassign属性を指定する。

@property (retain) NSString* title;
@property (assign) id delegate;

つまり、上に示した例は、次のソースコードと同等になるだろう。

// retain属性を指定した場合
- (void)setTitle:(NSString*)title
{
    if (_title != title) {
        [_title release];
        _title = [title retain];
    }
}

// assign属性を指定した場合
- (void)setDelegate:(id)delegate
{
    _delegate = delegate;
}

ガベージコレクションを有効にしている場合は、このような面倒なことを気にする必要はない。retainメソッドは無効化されており、参照しているかどうかで、そのオブジェクトが解放されるかどうかが決まるからだ。

もう1つ、オブジェクトの保持に関する属性がある。copy属性だ。これは、指定されたオブジェクトを、コピーして保持することになる。

@property (copy) NSString* value;

対応するコードは、次のようになるだろう。

- (void)setValue:(NSString*)value
{
	[_value release];
	 _value = [value copy];
}

このretain、assign、copyの使い分け方だが、少しややこしい。まずガベージコレクションが無効の状態では、必ずどれか1つを指定しよう。指定しないとコンパイル時に警告が発生して、assignが適用される。

それに対してガベージコレクションが有効の場合は、必ずしも指定しなくていい。その場合、assignになる。ただい、これが嫌らしいのだが、属性を設定しないときに、そのプロパティの型がNSCopyingプロトコルに準拠していると、「属性が指定されていないからassignを適用するが、このオブジェクトはコピー可能だから、コピーしなくともいいのか?」といった意味の警告が発生する。

結局、ガベージコレクションが有効であっても、assignかcopyかを明示的に指定しておいた方が面倒が少ないだろう。ガベージコレクションを無効にしてretainやcopyを指定した場合は、deallocメソッドでこれらをreleaseするのを忘れないようにしよう。