Swift

Swiftで参照型の値から生ポインタを作る方法

More than 1 year has passed since last update.

Swiftで参照型の値から生ポインタを作る方法や、関連トピックについて説明します。

バージョン

Apple Swift version 4.0 (swiftlang-900.0.54.11 clang-900.0.31)
Target: x86_64-apple-macosx10.9

Unmanagedを使う

Swiftには Unmanaged<T> という型があります(リファレンス)。

ここでいう manage はメモリ管理のことです。Swiftのメモリ管理は参照カウンタによって実現されていますが、この参照カウンタの操作はコンパイラが自動的に生成しており、プログラマが明示的に行うことはありません。Unmanaged<T> は参照型の値をこの管理下から外すためのハンドラであり、参照型の値 T に対して、明示的な参照カウンタ操作を呼び出せます。例えば、以下のコードは Cat のインスタンスに対して retainrelease を呼び出すものです。

let cat: Cat = Cat()
let unmanagedCat: Unmanaged<Cat> = Unmanaged<Cat>.passUnretained(cat)
unmanagedCat.retain()
unmanagedCat.release()

Unmanaged には UnsafeMutableRawPointer を返すメソッド toOpaque() があるので、これを使って生ポインタが得られます。以下に例を示します。

let cat: Cat = Cat()
let unmanagedCat: Unmanaged<Cat> = Unmanaged<Cat>.passUnretained(cat)
let rawCat: UnsafeMutableRawPointer = unmanagedCat.toOpaque()

Unmanaged はこのように、参照型と生ポインタの両方と相互に変換できます。また、参照型の変換の際は、変換と同時に参照カウンタの増減を行うかどうかのバリエーションがあります。そのため、全部で6通りの変換があります。

  • 参照型からUnmanagedへ変換、カウンタを+1する: static func passRetained(_ value: Instance) -> Unmanaged<Instance>
  • 参照型からUnmanagedへ変換、カウンタはそのまま: static func passUnretained(_ value: Instance) -> Unmanaged<Instance>
  • Unmanagedから参照型へ変換、カウンタを-1する: func takeRetainedValue() -> Instance
  • Unmanagedから参照型へ変換、カウンタはそのまま: func takeUnretainedValue() -> Instance
  • Unmanagedから生ポインタへ変換: func toOpaque() -> UnsafeMutableRawPointer
  • 生ポインタからUnmanagedへ変換: static func fromOpaque(_ value: UnsafeRawPointer) -> Unmanaged<Instance>

unsafeBitCastを使う

参照型の値は内部的にはポインタです。そして、 UnsafeMutableRawPointer のメモリ表現もポインタそのものです。なので、 unsafeBitCast で変換できます。以下に例を示します。

let cat: Cat = Cat()
let rawCat: UnsafeMutableRawPointer = unsafeBitCast(cat, to: UnsafeMutableRawPointer.self)

ただしこれは参照型の値と UnsafeMutableRawPointer のメモリ表現が現在のところたまたま同じであるから動くだけです。Swiftの規格上において動作が保証されるものではないので、使用は推奨しません。

ObjectIdentifierを使う

もし生ポインタを使用したい理由が、参照先インスタンスの同一性にもとづいて、辞書のキーを構築するなどであれば、 ObjectIdentifier を使うという選択肢もあります(リファレンス)。これは同一性に基づく値が欲しいだけである場合には最適な選択肢です。今のところそのような予定は全く無いですが、もしもSwiftがコピーGCを実装した仮想マシン上で実行されるような事があれば、インスタンスアドレスはメモリマネージャによって変更される可能性があります。そのようなアドレスが変化する環境においても、ObjectIdentifier は同一の値でありつづけるでしょうから、コードの意味上もこちらが適切なのです。ただまあ、現状のIdentifierの実装はポインタのアドレス値です(ソース)。

ポインタのアドレス値を取り扱う

もしポインタが指しているアドレスの整数値が取得したいのであれば、 Int.init(bitPattern: UnsafeMutableRawPointer?) で変換できます(リファレンス)。逆に、アドレスの整数値からポインタを生成したいのであれば、 UnsafeMutableRawPointer.init?(bitPattern: Int) が使えます(リファレンス)。