Cleaning up stdlib Pointer and Buffer Routines
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 の親クラスとして ManagedProtoBuffer(header を持たないバージョン)が用意されていました。
// 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]Pointer に to: ラベルを付与
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 に書き換えれば済みます。