NullabilityとObjective−C
Xcode6.3ではSwiftとObjective-Cの連携を強化するために、Objective-Cの新機能としてnullabilityアノテーションが導入されました。
NullabilityとObjective-C
Swiftの特徴の一つとして、Objective-Cのコードと透過的に連携できることです。Objective-Cの既存のフレームワークでもあなたの書いたコードでも連携できます。しかしSwiftではOptionalと非Optionalとを厳格に区別しています。例えばNsViewとNSView?があります。Objective-CではどちらもNSView *とできます。SwiftコンパイラはあるNSView *がOptionalかどうかを判断できないので、明示的にアンラップされたOptionalであるNSView!に置き換えられます。
以前のXcodeでは、アップル提供のフレームワークのいくつかでは、APIがSwiftのOptionalが正しく変換できているかを特別に確認していました。Xcode6.3ではObjective-Cの新しい言語仕様であるnullabilityアノテーションを使うことで、あなた自身が書いたコードでもサポートされます。
機能の核心:__nullableと__nonnull
この機能の核心は新しい2つのアノテーション、__nullableと__nonnullです。想像がつくとは思いますが、__nullableがついたポインターはNULLやnil値になりえますが、__nonnullだと許されません。ルールに反した場合はコンパイラが知らせてくれます。
@interface AAPLList : NSObject <NSCoding, NSCopying>
// ...
- (AAPLListItem * __nullable)itemWithName:(NSString * __nonnull)name;
@property (copy, readonly) NSArray * __nonnull allItems;
// ...
@end
// --------------
[self.list itemWithName:nil]; // warning!
__nullableと__nonnullは通常のCのconstが使用できるところではほとんど使用できます。もちろんポインタ型である必要はあります。しかし通常のケースではこのアノテーションを使用するのにもっといい方法があります。型が単純なオブジェクトであるかブロックへのポインタであれば、メソッドの宣言内でアンダースコアのつかないnullableとnonnullをかっこ開きのすぐ後に書けばいいのです。
- (nullable AAPLListItem *)itemWithName:(nonnull NSString *)name;
- (NSInteger)indexOfItem:(nonnull AAPLListItem *)item;
プロパティの場合、プロパティ属性のリストにアノテーションを移すことで同じくアンダースコアのつかないスペルのnullableとnonnullを使用します。
@property (copy, nullable) NSString *name;
@property (copy, readonly, nonnull) NSArray *allItems;
アンダースコアのない書き方はアンダースコアのつく書き方よりいいですが、ヘッダーファイルのすべての型に追加しなければならないことには変わりません。作業を簡単にしヘッダーファイルを簡潔にしておくために監査範囲(audited regsions)を使用したくなるはずです。
監査範囲(audited regsions)
2つのアノテーションを簡単に利用するためにObjective-Cのヘッダーファイルの特定の範囲をnullabilityのために監査されたということを記述することができます。この範囲内では単純なポインタ型はnonnullであると扱われます。これを利用すると上記にあげた例をずっと簡単に書き換えることができます。
NS_ASSUME_NONNULL_BEGIN
@interface AAPLList : NSObject <NSCoding, NSCopying>
// ...
- (nullable AAPLListItem *)itemWithName:(NSString *)name;
- (NSInteger)indexOfItem:(AAPLListItem *)item;
@property (copy, nullable) NSString *name;
@property (copy, readonly) NSArray *allItems;
// ...
@end
NS_ASSUME_NONNULL_END
// --------------
self.list.name = nil; // okay
AAPLListItem *matchingItem = [self.list itemWithName:nil]; // warning!
安全面からこのルールにはいくつかの例外があります。
- typedef型は通常では性質的にnullabilityを持つことができません。状況によってNULL可能にもNULL不可能にもなります。そのためtypedefは監査範囲にあってもnonnullとは扱われません。
- id *のような複雑なポインタの型は明示的にアノテーションをつける必要があります。例えば、NULLであってはいけないポインタをNULL可能なオブジェクトに参照をさせるには__nullable id * __nonnullとします。
- NSErorr *という型はメソッドのパラメータ経由でエラーを返すのに使用されます。NULLであることが可能なNSErorrへの参照であるNULLが可能なポイントとして常に扱われます。
詳細はエラーハンドリングプログラミングガイドを参照して下さい。
互換性
あなたが作成したObjective-Cフレームワークの既存のコードについてはどうでしょうか。このような型変換は安全なのでしょうか。答えは安全です。
- あなたが作成したフレームワークを使用している既存のコンパイルされたコードは問題なく動きます。つまりABI(アプリケーション・バイナリー・インタフェース)は変更がありません。既存のコードでは実行時に間違ってnilを渡しても受け取りません。
- あなたが作成したフレームワークを使用している既存のソースコードに安全でない使い方があれば、新しいSwiftコンパイラでのコンパイル時に新たに警告が出されるでしょう。
- nonnullは最適化には影響がありません。nonnullと指定されたパラメータに実行時に実際にnilが入っているかどうかをチェックすることができます。上位互換性のために重要なことです。
一般的にはnullableやnonnullについては、現在アサーションや例外を使用して確認しているように、ざっとチェックしておくべきです。ルールに反することはプログラマーの責任です。戻り値については特にコントロールするべきで、上位互換性の目的でない限りは、NULLであってはいけない戻り値にnilを返してはいけません。
Swiftに戻って
Objective-Cのヘッダーにnullabilityアノテーションを付け加えて、Swiftから使用してみましょう。
アノテーション前:
class AAPLList : NSObject, NSCoding, NSCopying {
// ...
func itemWithName(name: String!) -> AAPLListItem!
func indexOfItem(item: AAPLListItem!) -> Int
@NSCopying var name: String! { get set }
@NSCopying var allItems: [AnyObject]! { get }
// ...
}
アノテーション後:
class AAPLList : NSObject, NSCoding, NSCopying {
// ...
func itemWithName(name: String) -> AAPLListItem?
func indexOfItem(item: AAPLListItem) -> Int
@NSCopying var name: String? { get set }
@NSCopying var allItems: [AnyObject] { get }
// ...
}
Swiftのコードが分かりやすくなりました。些細な変化ですがフレームワークを使いやすくなっています。
CとObjective−CへのnullabilityアノテーションはXcode6.3で使用可能です。詳しくはXcode6.3のリリースノートを確認して下さい。