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

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

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

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

## Storyboard
![Memory_Leak_Storyboard.png](https://qiita-image-store.s3.amazonaws.com/0/122007/b88bbcbc-bfa3-cef7-6fee-78031d3b806d.png)
一覧画面・詳細画面の2画面を作成しています。

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

```swift: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それぞれが強参照でお互いを参照しあう状態を作成します。

```swift: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に伝える設計を想定します。
処理は特に何もさせておらず、成功した風の雰囲気で実装しています。

```swifit: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](https://qiita-image-store.s3.amazonaws.com/0/122007/c91323ff-2bd9-0340-a182-018bc2bb919f.png)

こちらのボタンを押すと、押したタイミングでのメモリ等の状況が可視化できます。
以下のような画面です。
![debug_memory_graph_display.png](https://qiita-image-store.s3.amazonaws.com/0/122007/441c2d03-5683-0244-df4e-f2752f15dd21.png)

また、画面左下のボタンでNavigator上の表示をメモリリーク箇所のみの表示に絞ることもできます。
この時点ではメモリリークは発生していないのでなにも表示されません。
![debug_memory_graph_only_leak.png](https://qiita-image-store.s3.amazonaws.com/0/122007/cf9fc57c-282a-4f59-6afd-1377efea0619.png)

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

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

上記操作後、再度Debug Memory Graphを起動し、メモリリーク箇所だけに絞ってみます。
![debug_memory_graph_show.png](https://qiita-image-store.s3.amazonaws.com/0/122007/d13d8895-68f3-6c63-1e54-ec0cdb9dc994.png)

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

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

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

```swift:SomeAPICaller修正前
class SomeAPICaller {
    // viewControllerの強参照
    var viewController: DetailViewController?
    ...
}
```

```swift:SomeAPICaller修正後
class SomeAPICaller {
    // viewControllerの弱参照
    weak var viewController: DetailViewController?
    ...
}
```
上記修正を行ったのち、複数回一覧画面と詳細画面の表示を繰り返しDebug Memory Graphを起動、メモリリーク箇所だけに絞ると、Navigator上には何も表示されません。
このことによりメモリリークが解消されていることが見てとれます。

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

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