Swift Digest
SE-0017 | Swift Evolution

Change Unmanaged to use UnsafePointer

Proposal
SE-0017
Authors
Jacob Bandes-Storch
Review Manager
Chris Lattner
Status
Implemented (Swift 3.0)

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 を要求するため、両者を橋渡しするには毎回 UnsafePointerCOpaquePointer を往復する必要がありました。

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()))
}

本質的にやりたいのは「Unmanagedvoid * 相当のポインタを行き来する」ことだけなのに、COpaquePointer という中間型が挟まることで記述が膨らんでしまっていました。COpaquePointer 自体も vestigial(残滓的)な存在と見なされており、C API とのブリッジは UnsafePointer 系に寄せていきたいという方向性もありました。

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

UnmanagedfromOpaque / 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: ...) を付けた形で残し、適切な書き換え先をメッセージで示すアプローチが取られました。