NullabilityとObjective−C

LINEで送る
Pocket

Xcode6.3ではSwiftとObjective-Cの連携を強化するために、Objective-Cの新機能としてnullabilityアノテーションが導入されました。

In previous Xcode releases, some Apple frameworks had been specially audited so that their API would show up with proper Swift optionals. Xcode 6.3 supports this for your own code with a new Objective-C language feature: nullability annotations.Swift blog

NullabilityとObjective-C

Swiftの特徴の一つとして、Objective-Cのコードと透過的に連携できることです。Objective-Cの既存のフレームワークでもあなたの書いたコードでも連携できます。しかしSwiftではOptionalと非Optionalとを厳格に区別しています。例えばNsViewNSView?があります。Objective-CではどちらもNSView *とできます。SwiftコンパイラはあるNSView *がOptionalかどうかを判断できないので、明示的にアンラップされたOptionalであるNSView!に置き換えられます。

以前のXcodeでは、アップル提供のフレームワークのいくつかでは、APIがSwiftのOptionalが正しく変換できているかを特別に確認していました。Xcode6.3ではObjective-Cの新しい言語仕様であるnullabilityアノテーションを使うことで、あなた自身が書いたコードでもサポートされます。

機能の核心:__nullableと__nonnull

この機能の核心は新しい2つのアノテーション、__nullable__nonnullです。想像がつくとは思いますが、__nullableがついたポインターはNULLnil値になりえますが、__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が使用できるところではほとんど使用できます。もちろんポインタ型である必要はあります。しかし通常のケースではこのアノテーションを使用するのにもっといい方法があります。型が単純なオブジェクトであるかブロックへのポインタであれば、メソッドの宣言内でアンダースコアのつかないnullablenonnullをかっこ開きのすぐ後に書けばいいのです。

- (nullable AAPLListItem *)itemWithName:(nonnull NSString *)name;
- (NSInteger)indexOfItem:(nonnull AAPLListItem *)item;

プロパティの場合、プロパティ属性のリストにアノテーションを移すことで同じくアンダースコアのつかないスペルのnullablenonnullを使用します。

@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が入っているかどうかをチェックすることができます。上位互換性のために重要なことです。

一般的にはnullablenonnullについては、現在アサーションや例外を使用して確認しているように、ざっとチェックしておくべきです。ルールに反することはプログラマーの責任です。戻り値については特にコントロールするべきで、上位互換性の目的でない限りは、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のリリースノートを確認して下さい。

LINEで送る
Pocket

Tags:

コメントを残す