[iOS 8] NS_DESIGNATED_INITIALIZER, NS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATIONマクロを使う
指定イニシャライザとプロトコル向けのマクロ
iOS 8 では新たに次の2つのマクロもNSObjCRuntime.hに加わっています。
#ifndef NS_DESIGNATED_INITIALIZER #if __has_attribute(objc_designated_initializer) #define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) #else #define NS_DESIGNATED_INITIALIZER #endif #endif #ifndef NS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATION #if __has_attribute(objc_protocol_requires_explicit_implementation) #define NS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATION __attribute__((objc_protocol_requires_explicit_implementation)) #else #define NS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATION #endif #endif
以前の記事でattributeディレクティブを扱いましたが、このマクロの実体であるディレクティブもObjective-Cのイニシャライザとプロトコルに条件を加えて、クラスの使用者やプロトコルの実装者に作成者の意図を伝えるものになっています。早速詳細を見ていきましょう
NS_DESIGNATED_INITIALIZER
このマクロの実体は__attribute__((objc_designated_initializer))で、クラスの使用者がどのイニシャライザが指定イニシャライザかを把握できます。
指定イニシャライザについてはSwiftのそれとほぼ同じで、ドキュメントに図も交えて詳しく解説されていますが、こちらでまた一度整理してみます。
- サブクラスの指定イニシャライザの実装部では必ずスーパクラスの指定イニシャライザを呼ぶ必要がある。
- 指定イニシャライザとして宣言されたイニシャライザが少なくともひとつあるクラスの場合、指定イニシャライザ以外のイニシャライザはコンビニエンスイニシャライザと呼ばれ、実装部では必ず同じクラスのイニシャライザを内部でselfにセットする必要がある。
- 指定イニシャライザが提供されているクラスではスーパクラスの指定イニシャライザをもれなく実装する必要がある。
- サブクラスで継承されるスーパクラスの指定イニシャライザについてもサブクラスで指定イニシャライザの扱いにするためには再度マクロで指定する必要がある。
この規則に従わないクラスについてはWarningが表示されるようになります。
実際にコードを動かして確認してみましょう。
@interface SomeSuper : NSObject - (instancetype)init NS_DESIGNATED_INITIALIZER; - (instancetype)initWithString:(NSString *)string NS_DESIGNATED_INITIALIZER; @end @implementation SomeSuper - (instancetype)init { self = [super init]; if (self) { } return self; } - (instancetype)initWithString:(NSString *)string { self = [super init]; if (self) { } return self; } @end @interface SomeSub : SomeSuper - (instancetype)init NS_DESIGNATED_INITIALIZER; - (instancetype)initWithString:(NSString *)string NS_DESIGNATED_INITIALIZER; - (instancetype)initWithNumber:(NSNumber *)number; @end @implementation SomeSub - (instancetype)init { self = [super init]; if (self) { } return self; } - (instancetype)initWithString:(NSString *)string { self = [super initWithString:string]; if (self) { } return self; } - (instancetype)initWithNumber:(NSNumber *)number { self = [self initWithString:number.stringValue]; if (self) { } return self; } @end
実際このコードはWarningを発しません。
指定イニシャライザのルールに合わせなかった場合にどのようなWarningがでるかを先ほどのルールに合わせて見ていきましょう。
ルール1: サブクラスの指定イニシャライザでは必ずスーパクラスの指定イニシャライザを呼ぶ
SomeSubのinitWithStringでselfへの代入を[self init]にしてみましょう。
ルール2: コンビニエンスイニシャライザでは必ず同じクラスのイニシャライザを内部でselfにセットする
SomeSubのinitWithNumberでselfへの代入を[super init]にしてみましょう。
ルール3: 指定イニシャライザの提供されるクラスではスーパクラスの指定イニシャライザをもれなく実装する
SomeSubのinitWithStringの宣言と実装をコメントアウトしてみましょう。
指定イニシャライザの実装が無いというWarningを発しています。
ルール4: サブクラスで指定イニシャライザの扱いにするためには再度マクロで指定する
SomeSubのinitWithStringの宣言をコメントアウトしてみましょう。
宣言がコメントアウトされたことでサブクラスではスーパクラスの指定イニシャライザがコンビニエンスイニシャライザとして扱われるようになったことがわかります。
NS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATION
次にプロトコルに追加できる新しいマクロについてです。
こちらのマクロも実体は__attribute__((objc_protocol_requires_explicit_implementation))で、このディレクティブをプロトコルに宣言することでプロトコルを準拠するクラスのサブクラスについてもプロトコル宣言が行われていれば明示的にプロトコルの実装(現在はプロパティのみ)を行うことを実装者に促すことができます。
先ほどと同じようにコードを確認してみましょう。
NS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATION @protocol SomeProtocol <NSObject> @property (readonly) id propertyForConfirmClass; @end @interface Super : NSObject <SomeProtocol> @end @implementation Super - (id)propertyForConfirmClass { return nil; } @end @interface Sub : Super <SomeProtocol> @end @implementation Sub - (id)propertyForConfirmClass { return [NSNull null]; } @end
この例を見てもあまりマクロのご利益が伝わらないかもしれませんので、ひとまず、Subの実装を消してみましょう。Superの方に実装はあるので特に明示的に実装をせずとも機能自体はSubに継承されていますので動作的には何ら問題がないはずです。しかし実際には次のようなWarningがでます。
暗黙的に実装が済んでいるはずのプロパティに対して実装がないというWarningがでています。
ここでさらにNS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATIONのWarningを消してみましょう。すると今度はWarningが消えます。
このマクロはクラスの実装者に対してプロトコルに準拠した場合は必ず明示的な実装を行うように促す役割を持っていることが分かります。
現在はプロパティにしかWarningを発しないようですが、将来的にはメソッドに対してもWarningを発することが期待されます。