この記事の目的
iOSアプリにおけるメモリリークの対処法を紹介します。
例えば、以下の画像のような、一見複雑な循環参照が原因のリークも簡単に直せるようになります。
ビルド前の準備
Edit Scheme
でMalloc Stack Logging
をONにしておきます。
これで各クラスインスタンス生成時に実行されたコードを見ることができます。詳細は後述します。
メモリリークの検知
デバッグビルドしてから、アプリを操作*1した後に'Memory Graph'を開きます。
Xcode左端のDebug Navigatorを見てみると、リークしているインスタンスには紫色の「!」がついています。
しかし、以下の画像のように、「!」がつかない場合もあります。*2
そのため、アプリの操作状況を鑑みて、不本意にインスタンスが残っていないか少し注意する必要があります。
メモリリークの原因を特定する
ここでの「メモリリークの原因」とはリークしているインスタンスを強参照しているコードのことです。
そのコードは基本的に参照元の生成時のStack Trace
から特定できることが多いです。
コードを探すためには以下のようにします。
リークしているインスタンスを、Debug Navigatorで選択してから参照元のノードをクリックします。
参照元インスタンスが生成されたときのStack Traceが右のインスペクタに表示されています。
Stack Trace
にカーソルを合わせると「→」が表示されます。
- 「→」をクリックすると、クリックしたフレームのコードに飛ぶことができるので 原因のコードが見つかるまで一つ一つ探します。
原因を特定するときのTips
- Optionを押しながら「→」をクリックすることで、Memory Graph画面を切り替えることなく、コードを表示することができます。
- どうしても原因のコードが見つからない場合は、他にリークしている別のインスタンスを選んで同じように詮索するとみつかる可能性があります。
メモリリークの修正方法
メモリリークの原因は基本的に強参照で循環参照しているときに起こります。よくあるのは以下のような形だと思います。
原因 | 修正方法 |
---|---|
Delegateを強参照で保持 | Delegateはweak修飾子を弱参照しましょう |
ハンドラー内部からselfを強参照 | ハンドラーに[weak self] をつけましょう |
インスタンスメソッドをハンドラーとして保持*3 | インスタンスメソッドを直接保持するのではなく、[weak self] を付与したハンドラーとして定義して保持しましょう。 |
まとめ
原因のコードに直接飛べることが、この方法のいいところだと思います。
その他備考
- 冒頭に紹介した循環参照の画像の例も同じ方法で解決できました。実はよく見ると参照元が2つしか無い為、原因の特定も簡単にできました。
- RxSwiftのflatMapなどでselfを強参照するとすぐにメモリリークを起こしますが、この方法で同じ用に解決できます。
- 記事を書くために作った実際にリークするXCODEプロジェクト https://github.com/po-miyasaka/LeakExample