Swift Digest
SE-0127 | Swift Evolution

Cleaning up stdlib Pointer and Buffer Routines

Proposal
SE-0127
Authors
Charlie Monroe
Review Manager
Chris Lattner
Status
Implemented (Swift 3.0)

01 何が問題だったのか

Swift 3 では API ガイドラインの整備が進められましたが、標準ライブラリのポインタ・バッファ関連 API にはガイドラインに沿っていない箇所や、役割が重複した宣言がいくつか残っていました。具体的には次の4点です。

withUnsafe[Mutable]Pointer の引数ラベル

withUnsafePointer / withUnsafeMutablePointer は、変数のアドレスをクロージャに渡すための関数です。Swift 3 以前はその第1引数にラベルが付いておらず、API ガイドライン(初出の引数にも意味のあるラベルを付ける)から外れていました。

// Before
withUnsafePointer(&x) { ptr in
    // ...
}

複数ポインタ版 withUnsafe[Mutable]Pointers

withUnsafePointers / withUnsafeMutablePointers という、複数の変数のポインタを一度に取る亜種も用意されていました。しかし用途は限定的で、単数版を入れ子にすれば同じ意味を簡単に書けるため、標準ライブラリに別 API として抱える価値が薄いものでした。

unsafeAddressOf

unsafeAddressOf(_:) はクラスインスタンスのアドレスを UnsafePointer<Void> として取り出すためのトップレベル関数です。しかし用途は限られており、同じ目的は Unmanaged.passUnretained で安全かつ明示的に表現できます。重複した API として整理対象になりました。

ManagedProtoBuffer

ManagedBuffer は、ヘッダ付きの可変長バッファをクラスとして確保するためのユーティリティで、ManagedBuffer.create(minimumCapacity:makingHeaderWith:) でインスタンスを作ります。このとき makingHeaderWith クロージャが呼ばれる時点ではヘッダがまだ初期化されておらず、そこで header プロパティにアクセスされるのを防ぐために、ManagedBuffer の親クラスとして ManagedProtoBufferheader を持たないバージョン)が用意されていました。

// Before
public class ManagedProtoBuffer<Header, Element> { /* header なし */ }
public class ManagedBuffer<Header, Element>: ManagedProtoBuffer<Header, Element> {
    public final var header: Header
    // ...
}

しかしこれは、プログラマのミスにすぎないアクセスを型システムで防ぐために、公開 API に専用の親クラスを増やしているという状態で、利用者にとっては ManagedProtoBuffer という余計な型が見えてしまい、API が無駄に複雑になっていました。

02 どのように解決されるのか

上記の4点をまとめて整理し、API ガイドラインに沿った形にします。

withUnsafe[Mutable]Pointerto: ラベルを付与

withUnsafePointer / withUnsafeMutablePointer の第1引数に to: ラベルを付けます。

// After
withUnsafePointer(to: &x) { ptr in
    // ...
}

複数ポインタ版の削除

withUnsafePointers / withUnsafeMutablePointers は削除されます。同じ処理は単数版を入れ子にして書きます。

var x = NSObject()
var y = NSObject()

withUnsafePointer(to: &x) { ptrX in
    withUnsafePointer(to: &y) { ptrY in
        // ptrX と ptrY を使った処理
    }
}

unsafeAddressOf の削除

unsafeAddressOf(_:) は削除され、Unmanaged.passUnretained を使うことが推奨されます。

// Before
let obj = NSObject()
let ptr = unsafeAddress(of: obj)

// After
let obj = NSObject()
let ptr = Unmanaged.passUnretained(obj)

Unmanaged を経由することで、参照カウント操作を行っていない(unretained である)ことが呼び出し側で明示されます。

ManagedProtoBuffer の削除

ManagedProtoBuffer クラスは削除され、そのメンバは ManagedBuffer に統合されます。ManagedBuffer.create(minimumCapacity:makingHeaderWith:) のクロージャ内で header にアクセスしてしまう問題は、型システムで防ぐのではなく、プログラマが注意すべき誤用として扱います。これにより、公開 API 上に見える型が ManagedBuffer ひとつだけになり、API がすっきりします。

ManagedProtoBuffer を明示的な型として参照していた箇所は、そのまま ManagedBuffer に書き換えれば済みます。