LoginSignup
96
79

More than 5 years have passed since last update.

Xcode8のDebug Memory Graphでメモリリークをビジュアル化

Last updated at Posted at 2016-09-25

概要

Xcode8から追加された「Debug Memory Graph」機能を用いてメモリリークをビジュアル化してみました。

想定したケース

  • 一覧画面・詳細画面の2画面構成
  • 詳細画面でAPI呼び出しを行う
  • API呼び出しクラスが呼び出し元のViewControllerのあるメソッドを呼ぶ設計になっている
  • 詳細画面のViewControllerクラスと、API呼び出しクラスが強参照で相互参照している

プログラム作成

メモリリークが発生するプログラムを作成します。

Storyboard

Memory_Leak_Storyboard.png
一覧画面・詳細画面の2画面を作成しています。

一覧画面

一覧画面は単純にTableViewで1行セルを表示し、セルをタップすると詳細画面に遷移するだけです。
遷移はStoryboard上でSegueを利用しています。
(例なので、値・Identifierは固定とし、force unwrapして表示させています。)

ListViewController.swift
class ListViewController: UITableViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return tableView.dequeueReusableCell(withIdentifier: "customCell")!
    }
}

詳細画面

メモリリークを発生させたいため、ViewController内であるAPIを呼ぶSomeAPICallerをインスタンス化し、ViewController、SomeAPICallerそれぞれが強参照でお互いを参照しあう状態を作成します。

DetailViewController.swift
import Foundation
import UIKit

class DetailViewController: UIViewController {
    // API呼び出しクラスの強参照
    var caller: SomeAPICaller = SomeAPICaller()

    override func viewDidLoad() {
        super.viewDidLoad()
        // API呼び出しクラスに自身を参照させる
        caller.viewController = self
        caller.call()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func updateLabelText(text: String) {
    }
}

API呼び出しクラス

APIを呼び出し、成功したらViewControllerに伝える設計を想定します。
処理は特に何もさせておらず、成功した風の雰囲気で実装しています。

SomeAPICaller.swift
import Foundation
import UIKit

class SomeAPICaller {
    // viewControllerの強参照
    var viewController: DetailViewController?

    // APIのコールを想定
    func call() {
        success()
    }

    // 成功したらViewControllerに伝える
    func success() {
        if let viewController = viewController {
            viewController.updateLabelText(text: "new text")
        }
    }
}

Debug Memory Graphでメモリリークをビジュアル化

準備が整いましたので、Debug Memory Graphを用いてメモリリークをビジュアル化してみます。

Debug Memory Graphの表示

Debug Memory Graphを表示してみます。
シミュレータでアプリを起動し、XcodeのDebug AreaからDebug Memory Graphの起動ボタンを選択します。
debug_memory_graph_icon.png

こちらのボタンを押すと、押したタイミングでのメモリ等の状況が可視化できます。
以下のような画面です。
debug_memory_graph_display.png

また、画面左下のボタンでNavigator上の表示をメモリリーク箇所のみの表示に絞ることもできます。
この時点ではメモリリークは発生していないのでなにも表示されません。
debug_memory_graph_only_leak.png

メモリリークの可視化

それでは、実際にメモリリークを発生させ、可視化してみたいと思います。
Debug Memory Graphを表示している状態だと、ブレークポイントで止まった状態と同様処理がストップしているので、Debug AreaのContinue program executionを押し再開させます。
再開したらシミューレータ上のアプリで以下操作を何度か繰り返します。

  1. 一覧画面の上でセルをタップし、詳細画面を表示
  2. ナビゲーションバー左の「一覧画面」をタップし、一覧画面を表示

上記操作後、再度Debug Memory Graphを起動し、メモリリーク箇所だけに絞ってみます。
debug_memory_graph_show.png

メモリリークが発生しているため、Navigator上にメモリリーク箇所が表示されます。
DetailViewControllerと、SomeAPICallerでメモリリークが発生しているのがNavigator上からも分かります。
また、上記操作を繰り返した数によって、DetailViewController、SomeAPICallerの末尾の括弧付きの数字が大きくなっており、保持されている数がどんどん増えていることが分かります。

それでは、この2クラス間の関係性がどうなっているのか。
それを見るために、Navigator上のDetailViewControllerのメモリリーク箇所をクリックします。
画面中央のEditor上に参照の関係性を示す画面が表示され、間をつなぐ矢印をクリックすると、相互で参照し合っている関係を見ることができます。

メモリリークの除去

メモリリークを可視化することができたため、そのメモリリークの原因となっている箇所を修正します。
SomeAPICallerのviewControllerプロパティをweakキーワードで修飾します。

SomeAPICaller修正前
class SomeAPICaller {
    // viewControllerの強参照
    var viewController: DetailViewController?
    ...
}
SomeAPICaller修正後
class SomeAPICaller {
    // viewControllerの弱参照
    weak var viewController: DetailViewController?
    ...
}

上記修正を行ったのち、複数回一覧画面と詳細画面の表示を繰り返しDebug Memory Graphを起動、メモリリーク箇所だけに絞ると、Navigator上には何も表示されません。
このことによりメモリリークが解消されていることが見てとれます。

まとめ

Debug Memory Graphを用いてメモリリークを可視化することができ、相互参照の関係性も見ることができました。
Xcode8で追加されたこのDebug Memory Graphはメモリリークを解消するためのツールとして強力で有用だと思います。
また、メモリリークは発生せずともクラス間の関係性、メモリの状況を見るツールとして利用できます。

今回のケースでは単純なケースを想定しましたが、より複雑なケースで利用したときにどこまで効果を発揮できるか、検証をしていかなければと感じています。
ここに関しては、実際継続して活用し、知見を溜めていきたいと思います。

96
79
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
96
79