理由
Swiftでは、? と! を使用して、オブジェクトまたはメソッドの引数がオプショナルか非オプショナルかを明示的に宣言しますが、Objective-C ではそのような区別はありません。Swift と Objective-C が混在している場合、Swift のコンパイラは、Objective-C のオブジェクトまたはメソッドの引数がオプショナルか非オプショナルかを知らないので、コンパイラは暗黙のうちに両方を非オプショナルとして扱います。メソッドの引数がオプショナルか非オプショナルかわからないので、コンパイラーは暗黙のうちに両方を非オプショナルとして扱います。
トローリング
この問題を解決するために、AppleはXcode 6.3で新しいObjective-Cの機能であるNullabilityアノテーションを導入しました。文字通り、__nullableはオブジェクトがNULLまたはnilであることを意味し、__nonnullはオブジェクトがNULLであってはならないことを意味します。このルールに従わない場合、コンパイラは警告を出します。 __nonnull/__nullable
Xcode 7では、サードパーティのライブラリとの潜在的な衝突を避けるために、Appleは_Nonnull/_Nullableに変更しました。 これは、Appleがアンダースコアなしでnonnull/nullableを書くこともサポートしているという事実と相まって、3つの書き方があるという混乱した状況を作り出しています。しかし、これら3つの書き方は、例えば以下のように場所が違うだけで、基本的には相互運用可能です:
//メソッドの戻り値の修飾子:
- (nullable NSString *)method;
- (NSString * __nullable)method;
- (NSString * _Nullable)method;
//プロパティ修飾子を宣言する:
@property (nonatomic, copy, nullable) NSString * aString;
@property (nonatomic, copy) NSString * __nullable aString;
@property (nonatomic, copy) NSString * _Nullable aString;
//メソッドパラメーターの変更:
- (void)methodWithString:(nullable NSString *)aString;
- (void)methodWithString:(NSString * _Nullable)aString;
- (void)methodWithString:(NSString * __nullable)aString;
__nonnull/__nullable
ダブルポ イ ン タ のオブジ ェ ク ト やブ ロ ッ ク の戻 り 値、 ブ ロ ッ ク の引数な ど では、 nonnull/nullable 修飾子は使え ません:
- (void)methodWithError:(NSError * _Nullable * _Nullable)error
- (void)methodWithError:(NSError * __nullable * __null_unspecified)error;
// その他の組み合わせ方
- (void)methodWithBlock:(nullable void (^)())block;
// 上記のnullableは、メソッドに渡されるBlock引数がnullであることを修飾するために使われているのであって、Blockの戻り値を修飾するために使われているのではないことに注意;
- (void)methodWithBlock:(void (^ _Nullable)())block;
- (void)methodWithBlock:(void (^ __nullable)())block;
- (void)methodWithBlock:(nullable id __nonnull (^)(id __nullable params))block;
// 上記のnullableは、メソッドに渡されるパラメータ・ブロックがnullであることを修飾するために使われている。 __nonnull ブロックの戻り値idがnullにならないように修正するために使う;
- (void)methodWithBlock:(id __nonnull (^ __nullable)(id __nullable params))block;
- (void)methodWithBlock:(id _Nonnull (^ _Nullable)(id _Nullable params))block;
// the method accepts a nullable block that returns a nonnull value
// there are some more combinations here, you get the idea
上記は、基本的にシナリオの使用の大半をリストアップしていますが、それを読んだ後、それはまだ混乱の顔であるああ、それはまだどの修飾子を使用する明確ではありません!
ネイティブiOS SDKのFoundationヘッダとUIKitヘッダ、そしてAppleのブログポスト「Nullability and Objective-C」を見て、私は次のような使い方の仕様を思いつきました:
- プロパティ、メソッドの戻り値、メソッドのパラメータを変更する場合は、nonnull/nullableを使用します;
__nonnull/__nullable
C関数のパラメータ、ブロックのパラメータ、ブロックの戻り値の変更には、_Nonnull/_Nullableを使用します。
Nonnull Audited Regions
すべてのプロパティやメソッドにnonnullやnullableを指定するのは面倒です。NS_ASSUME_NONNULL_BEGIN
NS_ASSUME_NONNULL_END
Appleはこの作業を軽減するために2つのマクロを用意しています。これらのマクロの間のコードでは、単純なポインタ・オブジェクトはすべてnonnullであると仮定されるので、null可能なポインタ・オブジェクトだけを指定すればよいことになります。これは次のコードに示されています:
NS_ASSUME_NONNULL_BEGIN
@interface myClass ()
@property (nonatomic, copy) NSString * aString;
- (id)methodWithString:(nullable NSString*)str;
@end
NS_ASSUME_NONNULL_END
上記のコードでは、aString プロパティはデフォルトで nonnull であり、methodWithString: メソッドの戻り値も nonnull であり、メソッドの引数 str は nullable として明示的に指定されています。
しかし、安全面を考慮し、アップル社は以下のルールを設けています:
- typedefで定義された型のnull可能性は通常文脈に依存し、Audited Regionsであってもnullでないと仮定することはできません;
__nullable id * __nonnull
複雑なポインタ型の場合、ポインタが非 null か nullable かを明示的に指定する必要があります;- 頻繁に使用される NSError ** は、通常 nullable NSError オブジェクトへの nullable ポインタであると仮定されます。
FAQ
__nonnull/__nullable
__nonnull/__nullable
Xcode 7では、Appleはオブジェクトがnullかどうかを修飾するために_Nonnull/_Nullableの使用を廃止することを提案していますが、最新のiOS 9.3 SDKのFoundationとUIKitヘッダでさえ、公式のネイティブクラスのメソッドパラメータはまだ.Null/_Nullableで修飾されていることがわかります。また、すでにnonnull/nullableがあるのに、なぜ_Nonnull/_Nullableを追加するのでしょうか?その理由は何でしょうか?Appleはブログポスト「 Nullability and Objective-C 」で説明していないので、StackOverflowにそれに関するディスカッションがあります:Difference between nullable, __nullable and _Nullable in Objective-C。C.
Posted on 2016-09-04 bykangzubin.com/nullability...