ペンギン村 Tech Blog

技術をこよなく愛するエンジニア集団が在住するペンギン村から、世界へ役立つ(かもしれない)技術情報を発信する技術系ブログです。某アラレちゃんが済む村とは一切関係ありません。んちゃ!

メモリリークを検出して修正する方法【Xcode】

この記事の目的

iOSアプリにおけるメモリリークの対処法を紹介します。
例えば、以下の画像のような、一見複雑な循環参照が原因のリークも簡単に直せるようになります。

f:id:po_miyasaka:20190815072055p:plain

ビルド前の準備

Edit SchemeMalloc Stack LoggingをONにしておきます。
これで各クラスインスタンス生成時に実行されたコードを見ることができます。詳細は後述します。

f:id:po_miyasaka:20190815033447p:plain
「Edit Scheme」で「Malloc Stack Logging」をONにする

メモリリークの検知

デバッグビルドしてから、アプリを操作*1した後に'Memory Graph'を開きます。

f:id:po_miyasaka:20190815035052p:plain
Memory Graphを開くボタン

Xcode左端のDebug Navigatorを見てみると、リークしているインスタンスには紫色の「!」がついています。 しかし、以下の画像のように、「!」がつかない場合もあります。*2
そのため、アプリの操作状況を鑑みて、不本意にインスタンスが残っていないか少し注意する必要があります。

f:id:po_miyasaka:20190815043530p:plain

メモリリークの原因を特定する

ここでの「メモリリークの原因」とはリークしているインスタンスを強参照しているコードのことです。
そのコードは基本的に参照元の生成時のStack Traceから特定できることが多いです。
コードを探すためには以下のようにします。

  1. リークしているインスタンスを、Debug Navigatorで選択してから参照元のノードをクリックします。

    f:id:po_miyasaka:20190815055011p:plain

  2. 参照元インスタンスが生成されたときのStack Traceが右のインスペクタに表示されています。 Stack Traceにカーソルを合わせると「→」が表示されます。

f:id:po_miyasaka:20190815060228p:plain:w200

  1. 「→」をクリックすると、クリックしたフレームのコードに飛ぶことができるので 原因のコードが見つかるまで一つ一つ探します。
    f:id:po_miyasaka:20190815061827p:plain
原因を特定するときのTips
  • Optionを押しながら「→」をクリックすることで、Memory Graph画面を切り替えることなく、コードを表示することができます。
  • どうしても原因のコードが見つからない場合は、他にリークしている別のインスタンスを選んで同じように詮索するとみつかる可能性があります。
メモリリークの修正方法

メモリリークの原因は基本的に強参照で循環参照しているときに起こります。よくあるのは以下のような形だと思います。

原因 修正方法
Delegateを強参照で保持 Delegateはweak修飾子を弱参照しましょう
ハンドラー内部からselfを強参照 ハンドラーに[weak self]をつけましょう
インスタンスメソッドをハンドラーとして保持*3 インスタンスメソッドを直接保持するのではなく、[weak self]を付与したハンドラーとして定義して保持しましょう。

まとめ

原因のコードに直接飛べることが、この方法のいいところだと思います。

その他備考

  • 冒頭に紹介した循環参照の画像の例も同じ方法で解決できました。実はよく見ると参照元が2つしか無い為、原因の特定も簡単にできました。
  • RxSwiftのflatMapなどでselfを強参照するとすぐにメモリリークを起こしますが、この方法で同じ用に解決できます。
  • 記事を書くために作った実際にリークするXCODEプロジェクト https://github.com/po-miyasaka/LeakExample

*1:今回はリーク確約されたViewControllerを開閉しました。

*2:理由不明

*3:インスタンスメソッドをハンドラーとして保持するとそのレシーバであるインスタンスも無条件で保持される。