Change Unmanaged to use UnsafePointer
01 何が問題だったのか
標準ライブラリの Unmanaged<Instance> は、ARC に参加しない型安全なオブジェクトラッパーで、利用者が手動で retain / release を呼ぶための仕組みです。この Unmanaged と生のポインタとを相互変換するための API として、当時は次の2つが用意されていました。
static func fromOpaque(value: COpaquePointer) -> Unmanaged<Instance>
func toOpaque() -> COpaquePointer
問題は、C の void * / const void * が Swift 側では UnsafePointer<Void> / UnsafeMutablePointer<Void> として公開される点です。C API から渡ってくるポインタや、C API に渡したいポインタは軒並み UnsafePointer 系の型になるのに対し、Unmanaged 側は COpaquePointer を要求するため、両者を橋渡しするには毎回 UnsafePointer と COpaquePointer を往復する必要がありました。
COpaquePointer を経由した変換が煩雑
結果として、Unmanaged と C API を組み合わせると次のようなコードになりがちでした。
someFunction(context: UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque()))
info.retain = { Unmanaged<AnyObject>.fromOpaque(COpaquePointer($0)).retain() }
info.copyDescription = {
Unmanaged.passRetained(CFCopyDescription(Unmanaged.fromOpaque(COpaquePointer($0)).takeUnretainedValue()))
}
本質的にやりたいのは「Unmanaged と void * 相当のポインタを行き来する」ことだけなのに、COpaquePointer という中間型が挟まることで記述が膨らんでしまっていました。COpaquePointer 自体も vestigial(残滓的)な存在と見なされており、C API とのブリッジは UnsafePointer 系に寄せていきたいという方向性もありました。
02 どのように解決されるのか
Unmanaged の fromOpaque / toOpaque が扱うポインタ型を、COpaquePointer から UnsafePointer<Void> / UnsafeMutablePointer<Void> に置き換えます。
新しいシグネチャ
fromOpaque は入力として UnsafePointer<Void> を、toOpaque は戻り値として UnsafeMutablePointer<Void> を取るようになります。
public static func fromOpaque(value: UnsafePointer<Void>) -> Unmanaged {
// null チェックは debug 時のみ
return Unmanaged(_private: unsafeBitCast(value, Instance.self))
}
public func toOpaque() -> UnsafeMutablePointer<Void> {
return unsafeBitCast(_value, UnsafeMutablePointer<Void>.self)
}
UnsafeMutablePointer の値は UnsafePointer を受け取る関数にもそのまま渡せるため、入力側は受け入れ幅の広い UnsafePointer<Void>、出力側は可変性を失わない UnsafeMutablePointer<Void> を選んでいます。
使い方
この変更により、先ほどのコードは COpaquePointer への明示的な変換が不要になります。
someFunction(context: Unmanaged.passUnretained(self).toOpaque())
info.retain = { Unmanaged<AnyObject>.fromOpaque($0).retain() }
info.copyDescription = {
Unmanaged.passRetained(CFCopyDescription(Unmanaged.fromOpaque($0).takeUnretainedValue()))
}
C API のコールバックで渡ってくる UnsafePointer<Void> をそのまま Unmanaged.fromOpaque に渡せますし、逆に Unmanaged.toOpaque() の結果をそのまま C API の void * 引数に渡せます。Unmanaged と C API の境界で中間型を挟む必要がなくなり、記述が素直になります。
既存コードへの影響
それまで COpaquePointer を使って Unmanaged を呼んでいたコードは、UnsafePointer / UnsafeMutablePointer を使うように書き換える必要があります。移行を支援するため、旧シグネチャは @available(*, unavailable, message: ...) を付けた形で残し、適切な書き換え先をメッセージで示すアプローチが取られました。