LoginSignup
13
13

More than 5 years have passed since last update.

Swiftのリフレクションでクロージャを扱う方法

Last updated at Posted at 2014-08-19

Xcode 6.1 Beta 3で仕様が変更され,Anyタイプの変数に関数タイプを代入できるようになりましたので,以下の話は無意味です。

 
 
関数のMirrorTypeから関数に戻す方法を試行錯誤してみました.
結果,いとも簡単に鏡の中(リフレクション)からクロージャを引っこ抜くことができました.

Swiftのリファレンスには

Any can represent an instance of any type at all, apart from function types.

とあり,確かに

let f: Any = {() -> () in println("Hello!")} 
//Segmentation fault: 11

Anyで関数タイプを表現できません(クラッシュするのはどうかとも思いますが).

しかし,リフレクション(reflect関数)を使って鏡の中(MirrorType)を覗くと,

let mr = reflect({() -> () in println("Hello!")})
println(mr.value)
// (Function)

しっかりと関数が入っています.MirrorTypevalueAnyタイプであるにも関わらず!
そして関数を取り出そうとすると,

let f = mr.value as () -> ()
// Segmentation fault: 11

やっぱり落ちます...うーむ...
 

unsafeBitCastを使って強引に引っ張り出してみる.

コマンドラインのswiftインタプリタでmr.valueを表示させると,

24> let s = mr.value 
s: Any = {
  payload_data_0 = {}
  payload_data_1 = {}
  payload_data_2 =
  instance_type = {}
}
25> sizeofValue(s)
$R9: Int = 32

ふむふむ.表示内容から見てpayload_data_の0,1にクロージャが入っていそうです.そしてサイズは全部で32バイトと出たのでpayload_data_instance_typeはそれぞれ8バイトずつと思われます.
一方,クロージャのサイズはsizeofすると16バイトと出ました.
unsafeBitCast関数でAnyから32(=16+8+8)バイトのタプルにコピーしてみましょう.

typealias AF = (() -> (), Int, Int)

let t = unsafeBitCast(s, AF.self)
let f = t.0

f()
// Hello!

無事,元の関数を取り出せました.強引ですけどね.

クロージャのキャスト関数を定義

このキャストは便利そうなので関数にしておきます.
(クロージャはどんなタイプでもサイズは16バイトなようなです.)

func anyToFuncCast<F>(any: Any, ftype: F.Type) -> F {
    assert(sizeofValue(any) == 32)
    typealias AF = (F, Int, Int)
    let t = unsafeBitCast(any, AF.self)
    return t.0
}

まとめ

  • 仕様的にありえないはず(?)のAnyタイプから関数タイプへの変換は,unsafeBitCastを使えば可能.MirrorTypeはドキュメントもないので仕様が適当なのでしょう.そのうち変わるかも.

  • このパターンのようにメモリ内容をだいたい把握できるときは,タプルとunsafeBitCastを使えば強引にキャストできるかも.

13
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
13