Objective-C のプロパティの属性を指定するとき従うべきガイドラインをまとめた。
できる限り nonatomic
を指定する
atomic
にしてもパフォーマンスが悪化するだけでほとんどメリットがない(参考:StackOverflow - Atomic vs nonatomic properties)。
@property (nonatomic) id value;
nonatomic
と atomic
の使い分けの指針は次のとおり:
- 参照型: メモリアドレスのみの書き込みなので、常にnonatomicでよい
- プリミティブ型:
- int, BOOL等ワンステップでの書き込みが可能: 常にnonatomicでよい
- 単一のスレッドからしかアクセスされない: 設計に気をつけつつnonatomic推奨
- 複数のスレッドからのアクセスがあり、long,構造体などサイズの大きい値: atomic推奨
複数のスレッドから同時に読み書きが行われる可能性があるプロパティには atomic
を指定する。 atomic
を指定することで getter と setter が排他的に実行されるようになり、値の書き込みに複数のメモリ操作命令が必要な型(構造体など)が中間状態で読み出されることがなくなる。
atomic
を指定してもスレッドセーフになるとは限らない点に注意すること。たとえば、 obj.atomicInt++
を複数スレッドで同時に実行すると、実行した回数より値が小さくなることがある(参考:Objective-Cでatomicな宣言プロパティがatomicであるとは限らない話|スマホとアドテク開発記)。
スレッドセーフ性を保証したいときは、 atomic
よりも @synchronized
や NSLock
などの排他制御のほうが適切なこともあるので、よく検討すること。
strong
と assign
は省略してもよい
オブジェクト型のプロパティは strong
が、それ以外の型のプロパティは assign
がデフォルトであるため、省略することができる。 weak
や copy
のプロパティと桁をそろえるために、あるいは既存のコードとスタイルを一致させるために、明示してもよい。
注:Xcode 4.2.xまでは、オブジェクト型プロパティの strong
の省略は認められない。
// 省略
@property (nonatomic) id object;
@property (nonatomic) NSInteger primitiveValue;
// または明示
@property (nonatomic, strong) id object;
@property (nonatomic, assign) NSInteger primitiveValue;
Outlet には基本的に weak
を指定する
Outlet が参照するオブジェクトは、大抵は何らかのビューのサブビューである。サブビューはそのスーパービューによって保持されるので、 outlet は weak
で構わない。 weak
にすることで意図しない循環参照を予防できる。ただし、次のようなオブジェクトを参照する outlet は strong
にしなければならない:
- Nib のトップレベルオブジェクト(ウィンドウ、他のビューに属さないビュー、
NSObjectController
などビュー以外のオブジェクト) - サブビューのうち、
-removeFromSuperview
を呼ぶなどして一時的にビュー階層から取り除かれる可能性があるもの
(参考:Managing the Lifetimes of Objects from Nib Files、[iOS5] ARC : Outletにはweakプロパティを使おう)
@property (nonatomic, weak) IBOutlet NSView *view;
@property (nonatomic, strong) IBOutlet NSWindow *window;
@property (nonatomic, strong) IBOutlet NSView *removableView;
デリゲートプロパティには weak
を指定する
あるオブジェクトをそのオブジェクト自身のデリゲートにするような状況では循環参照が発生するため、デリゲートプロパティを strong
にするとオブジェクトが解放されなくなる。特別な事情がなければデリゲートプロパティは weak
にすべきである。
Cocoa のデリゲートプロパティは、 NSURLConnection
や CAAnimation
などの例外を除き weak
か unsafe_unretained
になっている。
@property (nonatomic, weak) id delegate;
weak
参照してはいけないクラスに注意する
OS X では、 UI に関係するクラスのうちのいくつかが weak
参照に対応していない(参考:
weak 参照してはいけない Cocoa のクラス一覧)。これらのクラスを弱参照したい場合は unsafe_unretained
参照を使うこと。
Mutable なサブクラスがある型には copy
を指定する
NSString
型などの、対応する NSMutable...
型があるプロパティには copy
を指定する。Mutable なオブジェクトの copy
メソッドは immutable なオブジェクトを返すため、プロパティにセットされる値が immutable であることを保証できる。
Immutable オブジェクトの copy
メソッドは、オブジェクトをコピーせずインスタンス自身を返すため、パフォーマンスが悪化することはない。
@property (nonatomic, copy) NSString *string;
readonly
と対応するプロパティには readwrite
を明示する
あるプロパティをクラス外部からは readonly
にする場合、クラス内での定義には readwrite
を明示する:
/*** Foo.h ***/
@interface Foo
@property (nonatomic, readonly) id value;
@end
/*** Foo.m ***/
@interface Foo ()
@property (nonatomic, readwrite) id value;
@end
readwrite
はデフォルトであり省略可能だが、クラス外に readonly
で公開するプロパティだけ readwrite
を明示するルールにすると分かりやすい。
あるプロパティをヘッダと実装の両方に書くときはオーナーシップ属性を一致させる
下の例のようにオーナーシップ属性が一致していないと、 "ARC forbids synthesizing a property of an Objective-C object with unspecified ownership or storage attribute" とエラーが出る。
// 誤った指定の例
/*** Foo.h ***/
@interface Foo
@property (nonatomic, readonly) id value; // 暗黙的に `strong` を指定
@end
/*** Foo.m ***/
@interface Foo ()
@property (nonatomic, copy, readwrite) id value; // `copy` を指定
@end
ブロック型のプロパティには copy
を指定する
関数本体の中に記述されたブロックリテラルの記憶領域はスタックに確保される。ブロックオブジェクトをプロパティにセットするためには、 copy
メソッドを呼んで記憶領域がヒープに確保されたブロックを作らなければならない。
typedef void (^MyCallback)(void);
@property (nonatomic, copy) MyCallback callback;
ブロック型のプロパティにオーナーシップ属性を指定しないと、暗黙的な strong
にはならず、 "ARC forbids synthesizing a property of an Objective-C object with unspecified ownership or storage attribute" とエラーが出る。明示的に strong
を指定しても警告は出ないが、そうすべきではない。
BOOL 型のプロパティには is で始まる名前の getter を指定する
getter をメソッドとして呼び出すときは名前が is で始まるほうが自然。
@property (readonly, getter=isBlue) BOOL blue;
if (color.blue) { }
if (color.isBlue) { }
if ([color isBlue]) { }
(参考:Adopting Modern Objective-C)
まとめ
/*** Foo.h ***/
@interface Foo
// strong や assign は省略可
@property (nonatomic) id object;
@property (nonatomic) NSInteger primitiveValue;
// Outlet は weak にする(トップレベルオブジェクトは strong)
@property (nonatomic, weak) IBOutlet NSView *view;
@property (nonatomic, strong) IBOutlet NSWindow *window;
// Delegate は weak にする
@property (nonatomic, weak) id delegate;
// Mutable なサブクラスがある型は copy にする
@property (nonatomic, copy) NSString *possiblyMutable;
// 後述
@property (nonatomic, readonly) id internallyWritable;
@property (nonatomic, copy, readonly) id internallyWritableByCopy;
// ブロック型は copy にする
typedef void (^MyCallback)(void);
@property (nonatomic, copy) MyCallback callback;
// BOOL 型の getter 名は is で始める
@property (nonatomic, getter=isBlue) BOOL blue;
@end
/*** Foo.m ***/
@interface Foo ()
// 外部から readonly にするプロパティには readwrite を明示する
@property (nonatomic, readwrite) id internallyWritable;
@property (nonatomic, copy, readwrite) id internallyWritableByCopy;
@end
属性の順序について
属性を並べる順番は任意だが、既存のコードにプロパティを追加するなら周りのスタイルに合わせるべきである。新規に書く場合、筆者は以下の順番を推奨する:
@property (nonatomic, strong, readonly, getter=foo, setter=setFoo:) id foo;
nonatomic
はほぼ常に指定されるので、 @property (nonatomic
までを定型文としてとらえる。 strong
や weak
も(デフォルト値を省略しないなら)常に必要なので2番目に書く。 getter
と setter
は最後に指定する。仮にその後に属性を書くと、 getter/setter 名が長い場合に目で追いづらくなるだろう。