自己紹介
ペンギン村の通行人 、po_miyasakaです。
概要
できること
任意のタイミングでメモリ上に保持されているクロージャが定義されている場所を表示する。おそらく本邦初公開😎
使い所
手順がちょっと大変なので、どうしてもリークを確かめたい時とかに使えるかも。
方法
手順
Debug Memory Graphを開くclosureでコンポーネントを検索するとswift closure contextというクラスのインスタンスのアドレスが複数引っかかる- このインスタンスがクロージャにキャプチャされたデータなどのクロージャの情報のようである。
- このデータの中を探してもクロージャの場所はわからない。
- 任意のアドレスを左のバーから選ぶ。
- 末端の
swift closure contextを指している矢印をクリックして右のサイドバーのMemory Inspectorを表示 Sourceの項目には選択したswift closure contextの格納されているアドレスが表示されている。- 以下の画像の場合は
0x600003554100 + 24にswift closure contextのポインター0x6000035540e0が格納されていることを示している。- ポインターの指している中身を知る方法は後述する。

- ポインターの指している中身を知る方法は後述する。
- 実は
swift closure contextを指しているポインター0x600003554100 + 24の8byte前にクロージャ本体が格納されているのだ!- すなわち
0x600003554100 + 16のポインターの中身を見ることで、クロージャ本体の場所がわかる。
- すなわち
0x600003554100 + 16の中身をみると0x0000000101e86e20というアドレスが取れた。- このアドレスを使って
image lookup -a 0x0000000101e86e20というコマンドを叩く。すると以下のように出力される。
- この出力のSummaryを見ると
MainViewController.viewDidLoadに定義された一番目*1のクロージャで有ることがわかる。 - つまり以下のクロージャであることがわかる。

リファレンスからデリファレンスする方法
- リファレンスはポインターのことで、デリファレンスはポインターが指しているものを取得することです。
- 上記の8番における
0x600003554100 + 16がリファレンスで、これをデリファレンスすると0x0000000101e86e20が取得できるということです。
- 上記の8番における
x/gx コマンドを使う。
- 純粋にアドレスを取得することができる。

expression -l objc -o -- *(id**)を使う
- 長いのでエイリアスを作ったほうがいい*2
- この方法でいろんなポインタを見てみるとクラス名が表示されたり、
idをcharに変えると文字列が取得できたりするので面白い。


キャプチャされたデータが知りたい場合
上述の expression -l objc -o -- *(id**)がクラス名を表示してくれる特性を利用して、
以下の画像のようにswift closure context自体のメモリ領域を8バイトずつシラミ潰しにチェックしていけばキャプチャされているクラスが見つかるはず。*3

動作環境
- Xcode10
- iPhoneXR iOS12(simulator)
- プロジェクト
備考
- 動作確認したサンプルが少ないので、例外などを見つけ次第追記する。
