[Swift] Swift 3.0.1 の変更点

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Xcode 8.1 がリリース

Xcode 8.1 が正式リリースされました。iOS は iOS 10.1 向けの、macOS は macOS Sierra 10.12.1 向けのアプリケーションを作ることができます。特に macOS については MacBook Pro に搭載される Touch Bar が操れる NSTouchBar が SDK に追加されていたりで、何かと話題でしたね。

Xcode 8.1 には Swift のパッチバージョンとも言える Swift 3.0.1 が同梱されています。つまり Xcode 8.1 で使うことのできる Swift のデフォルトバージョンは 3.0.1 ということになります。

Swift 3.0 からどのような変更点があるか、リリースノートを参考にざっくり見ておきたいと思います。

新機能

nil を Objective-C の nonnull の id にブリッジするときに NSNull としてブリッジする

例えば次のような Objective-C のクラスがあり、

#import <Foundation/Foundation.h>

@interface SampleClass : NSObject
- (void)imported: (id _Nonnull)value;
@end
#import "SampleClass.h"

@implementation SampleClass
- (void)imported:(id)value {
    NSLog(@"%@", value);
}
@end

Swift 側ではこんな感じで呼んでみることとします。

let a: String? = "hello", b: String? = nil
SampleClass().imported(a)
SampleClass().imported(b)

このとき、 Swift 3.0 では _SwiftValue として渡されていました。

SE-0140-01

_SwiftValue は、Objective-C に対応する型がない場合に使われる型です。Optional が _SwiftValue として渡されてしまうと、値が空なのかどうなのかが解りづらく、バグを生みやすいという問題がありました。

そこで、Swift 3.0.1 では NSNull.null にブリッジされるようになりました。これで、値が空の場合は明確に判別できるようになりました。

SE-0140-02

Swift の数値型を Objective-C にブリッジするときに NSNumber としてブリッジする

これは SE-0140 に近い話です。Swift では数値の型は Int8 や UInt8、 Int16、 UInt16… と言ったように、ビット幅に合わせた型が用意されています。また、Float や Double と言った浮動小数点型もあります。

これらを Objective-C にブリッジする場合、Swift 3.0 では _SwiftValue として渡されていました。Swift 3.0.1 からは、標準的な数値の型はすべて NSNumber にブリッジされるようになりました。

SE-0139-01

また、CGRect や CGFloat なども同様の変更がありました。Swift 3.0 では _SwiftValue として渡されていましたが、Swift 3.0.1 からは NSValue にブリッジされるようになりました。

SE-0139-02

UnsafeRawBufferPointer と UnsafeMutableRawBufferPointer の追加

UnsafeRawBufferPointerUnsafeMutableRawBufferPointer が、新しい型として Swift Standard Library に追加されました。

これらはC言語互換の、ポインタを扱うための型のひとつです。Swift 3.0 では UnsafeRawPointer が導入されましたが、これのバッファ版、つまり固定的なメモリ領域(バッファ)を確保することのできる型です。既に UnsafeBufferPointer がありますが、これの UnsafeBufferPointer を置き換えるようなものです。

例えば、Int の配列 (Array) から UnsafeMutableRawBufferPointer の配列を作るには、次のように書きます。

let intArray = [1, 2, 3]
var byteBuffer = [UInt8]()

intArray.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in
    byteBuffer += bytes
}

// [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0] になる
byteBuffer

Pointer 系から RowPointer 系へ移行するガイドは、以下で公開されています。より詳しく知りたい方は参考にしてください。

__swift__ マクロの追加

__swift__ マクロを使って、Swift のバージョンを Objective-C 側で判別できるようになりました。例えば、以下のように判定します。

#if !defined(__swift__) || __swift__ >= 30001
    NSLog(@"Swift 3.0.1 以上だよ");
#else
    NSLog(@"Swift 3.0.1 未満だよ");
#endif

上記コードを Xcode 8.1 で実行すると「Swift 3.0.1 以上だよ」がコンソールに出力されます。

swift_macro

5桁の整数で表現されています(XXYZZ)。現時点では Swift 3.0.1 以上かどうかしか判別できませんが、今後は Swift のバージョンアップに応じて、Objective-C 側で条件分岐させることができます。今回のアップデートのように、型のブリッジがちょっと変わったりすることがあるので、有用そうです。

バグ修正

  • @NSManaged プロパティをプロトコルで使用するときの問題の解消
  • Objective-C のプロパティ使用時、 Xcode 8 で コンパイルした LTO ファイル (Link Time Optimization) と 旧 Xcode でコンパイルした LTO ファイルをリンクできない問題の解消
  • swift_error(zero_result) がハンドリングできない問題の解消
  • プロトコルに定義したメンバーをオーバーライドしたときのアクセスチェック時にクラッシュする問題を解消 (Xcode 8.0 と互換性を保つ場合はクラスやストラクチャの定義のトップレベルに private を使う必要あり)
  • UnsafePointer.withMemoryRebound(to:capacity:) を通して得られる値の型が UnsafePointer ではなく UnsafeMutablePointer になっていた問題を解消 (つまり Swift 3.0.1 では UnsafePointer が得られる)

まとめ

Objective-C とのブリッジの話が多かったです。Objective-C をまだ捨てていない、というよりは Swift に移行しやすいように環境を整えている印象を受けました。

参考