Swiftには実行時に型情報を保持するためのType metadataという仕組みがあります。 我々が頻繁に直接使うことはありませんが、Swiftのランタイムの動作を理解するための重要な要素です。 このトークでは Type metadata やその実装について紹介します。
こんにちは、家庭の医学くんという名前なのでそのように読んでください。メルカリでインターンをしています、おととい高校を卒業しました。
メタデータはSwiftを理解するのに重要です
runtimeには実は動的な部分が多くあります。
こんなコードを書いたことがあるでしょう
CellをClassと合わせるためにこんなコードを書いたことがあるのでは
Swiftのランタイムのメノリ表現を考えてみませんか
メタデータとはそもそもなにか
アプリ開発では馴染みがないかもしれません。
Swiftコア機能は内部的にmetadataをつかっていて実はいつも恩恵を受けています。
内部でどう使っているか解説します。
どのようにメタデータを使うか、ハックを紹介します
メタデータは型についてのSwiftの内部データです。enumのケース数など。バイナリ上に静的に格納されていたり、runtimeに動的に生成されたりします。
メタタイプが渡されたら型名を返します
CustomStringConvertibleに対応しているはずはない
つまりどこかに魔法があるはず
イニシャライザはSwiftCoreにある。Swift runtimeに魔法が実装されている。
SwiftCoreにStringやIntなど基本データ型とSequenceなどのプロトコルが実装されている。
SwiftRuntimeはC++で書かれている。ランタイムの振る舞いがそこで実装されている。動的な機能はここに実装されている。
SwiftはOpensourceなのでGithubで見られる
引数の型がMetaのとき、typeNameが呼び出され、getTypeNameが呼ばれている
関数の実装がない。
_silgen_nameはリンク時につけられる関数の名前を指定します。実装の解説は省略しますが、単純にメタデータに含まれている型名を取り出すだけです。
メモリ上でどのように表現されるのか。引数の型がメタデータの場合
ValueWitnessTableで見ていくことができる
Nominal Type Descriptorは型の詳細な情報を所持している。
Generic typeのばあい型パラメータも動的に埋め込まれる
つまりNominalTypeDescriptorから型の名前を取得できる
実装するのは難しくなさそうです。
SwiftコードでSwiftランタイムのイニシャライザの実装を実行してみましょう
メモリレイアウトをStructで再現します。
メモリレイアウトはドキュメントにありますが、古くなっているため、Swfitコンパイラを直接読む必要があります。
今回はStructに限定します
RelativePointerは参照先のアドレスを持っているのではなく、自身から参照先までのオフセットを所持しています。参照するときはオフセット分を進めるだけです。
下準備が出来たので型名を取り出してみましょう
引数から渡ってきたMetadataをMetadataのポインタにキャストします。ここではSwiftのサブタイピング関係がないため、Unsafebitcastを使います
SwiftのStringに変換して返します。
メタデータを用いた第一歩
メタデータは動的なふるまいのために使われます。どこにあるのでしょう。インスタンスのアロケート、プロトルやClassを介してメソッドを呼び出されると、メタデータの関数テーブルから呼び出したいメソッドのアドレスがとれる
ほかに、ミラーAPIがあげられます。
とても利便性が高いことが分かると思います。
今度は黒魔術を再現してみましょう。Metadataを理解することで大きな力を手にできます
タイプネームを取得したときと同じ要領で
関数ポインタを書き換える
Swizzingするメソッドのポインタと入れ替える
ちゃんと動きます。
上2つは?メタデータにアクセスするSwiftyな方法を提供してくれる
Codableでできるが、HandyJSONはCodableが登場する前から動いている。
Objective-Cなしで動的なプロパティと値のヒモ付にMetadataを使う
最後のライブラリは私の
たくさんのフィールドを持つStructでも引数無しで簡単にスタブをインスタンス化することができる。
この機能の大部分はCodableを使って実装されいてるが、いくつかはMetadataを使って実装されている。
事例を紹介するまえに、スタブインスタンス化機能がどのように動いているか紹介します。
基本的に木構造で成り立っています。デコーダプロトコルを使っている
葉の部分を使えば引数無しで注入できる
enumはユーザーが定義できるので自分で定義したenumのすべての値注入を用意する必要がある
Int型の値をキャストすればenumのケースが得られる。
スタブする型がenumかチェックする。そこでMetadataが使える。
4.2ではABIの安定性がありません。Metadataのレイアウトが5では崩れます。
しかし良いこともあります。Swfit5でABI安定が達成されました。
ということで今後は気軽にMetadataを使ったライブラリを作ることが出来ます。
4と5をサポートしたければかなり苦労するでしょう。
大いなる力を手に入れました。Metadataを使うことはオフィシャルな方法ではありません。Metadataを使うときは、とくに書き換えを行う場合は、正しい知識を持っておく必要があります。MethodSwizzlingはSwiftの型による最適化でもとの実装に戻ってしまうことがあります。
たとえばRevertualizeで動的呼び出しが静的呼び出しになる場合があり、こうしたケースをきちんとハンドリングする必要がある。
大いなる力には大いなる責任が伴うということです。
Swiftは動的ディスパッチにMetadataを使っています
メモリレイアウトを再現すればMetadataを使えます
皆さんのメタデータを使った素晴らしいライブラリを楽しみに待っています。