自己紹介
ペンギン村の通行人 、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)
- プロジェクト
備考
- 動作確認したサンプルが少ないので、例外などを見つけ次第追記する。