Objective-C のプロパティ属性のガイドライン

  • 893
    いいね
  • 6
    コメント
この記事は最終更新日から1年以上が経過しています。

Objective-C のプロパティの属性を指定するとき従うべきガイドラインをまとめた。

できる限り nonatomic を指定する

atomic にしてもパフォーマンスが悪化するだけでほとんどメリットがない(参考:StackOverflow - Atomic vs nonatomic properties)。

@property (nonatomic) id value;

nonatomicatomic の使い分けの指針は次のとおり:

  • 参照型: メモリアドレスのみの書き込みなので、常にnonatomicでよい
  • プリミティブ型:
    • int, BOOL等ワンステップでの書き込みが可能: 常にnonatomicでよい
    • 単一のスレッドからしかアクセスされない: 設計に気をつけつつnonatomic推奨
    • 複数のスレッドからのアクセスがあり、long,構造体などサイズの大きい値: atomic推奨

(thx to @takasek)

複数のスレッドから同時に読み書きが行われる可能性があるプロパティには atomic を指定する。 atomic を指定することで getter と setter が排他的に実行されるようになり、値の書き込みに複数のメモリ操作命令が必要な型(構造体など)が中間状態で読み出されることがなくなる。

atomic を指定してもスレッドセーフになるとは限らない点に注意すること。たとえば、 obj.atomicInt++ を複数スレッドで同時に実行すると、実行した回数より値が小さくなることがある(参考:Objective-Cでatomicな宣言プロパティがatomicであるとは限らない話|スマホとアドテク開発記)。

スレッドセーフ性を保証したいときは、 atomic よりも @synchronizedNSLock などの排他制御のほうが適切なこともあるので、よく検討すること。

strongassign は省略してもよい

オブジェクト型のプロパティは strong が、それ以外の型のプロパティは assign がデフォルトであるため、省略することができる。 weakcopy のプロパティと桁をそろえるために、あるいは既存のコードとスタイルを一致させるために、明示してもよい。

注: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 のデリゲートプロパティは、 NSURLConnectionCAAnimation などの例外を除き weakunsafe_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 までを定型文としてとらえる。 strongweak も(デフォルト値を省略しないなら)常に必要なので2番目に書く。 gettersetter は最後に指定する。仮にその後に属性を書くと、 getter/setter 名が長い場合に目で追いづらくなるだろう。