XCTestを利用してメモリーリーク検出を自動化する。

# 環境
OS: macOS 13.4.1
Xcode.app: Version 14.3.1 (14E300c)
動作iOSバージョン:14.0以上

# XCTestを利用してメモリーリーク検出する。
## メモリーリークを引き起こすViewControllerを用意する。
retain cycleが起きるようなコードを加えたViewControllerを作成します。

```Swift
class LeakingViewController: UIViewController {

    lazy var button = UIButton(primaryAction: UIAction(handler: { _ in
        self.onButtonAction()
    }))

    override func viewDidLoad() {
        view.addSubview(button)
    }

    func onButtonAction() {
        print("")
    }
}
```

## メモリーリーク検出を行うコードを追加する。
addTeardownBlockはドキュメントからテストが終わった後に実行するブロックを登録するとあるのでviewControllerにメモリーリークがなければ、参照は残らずにnilになるはずなのでXCTAssertNilをかけてnilであれば"Object should be deallocated. Detected memory leak."というメッセージが出るようにします。

![addTeardownBlock](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519522/8e5efa71-3f0f-a5c6-69d1-aa0ce1c7f496.png)

```Swift
func testExample() throws {
    let viewController = LeakingViewController()
    _ = viewController.view
    addTeardownBlock { [weak viewController] in
        XCTAssertNil(viewController, "Object should be deallocated. Detected memory leak.")
    }
}
```

テストを実行します。下記のように検出できました。

![Result001](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519522/3c7d267e-2767-fb84-0868-8a6d90152d4d.png)


LeakingViewControllerのコードを修正します。

```Swift
lazy var button = UIButton(primaryAction: UIAction(handler: { [weak self] _ in
    self?.onButtonAction()
}))
```

テストを実行します。
テストが成功しメッセージは消えます。

## テスト対象を増やした場合

検出はうまくいきましたがテスト対象が増えた時はどうなるのでしょうか？。
テスト項目を増やしてみます。

Presenterを持つViewControllerにretain cycleが起きるようなコードを加えました。

```Swift
protocol LeakingViewControllerDelegate: AnyObject {
    func buttonClicked()
}

protocol LoadingView {
    func startLoading()
}

class LeakingViewControllerPresenter: LeakingViewControllerDelegate {
    var view: LoadingView

    init(view: LoadingView) {
        self.view = view
    }

    func buttonClicked() {
        view.startLoading()
        //do some operation ...
    }
}

class LeakingViewControllerTwo: UIViewController, LoadingView {

    var delegate: LeakingViewControllerDelegate?

    lazy var button = UIButton(primaryAction: UIAction(handler: { [weak self] _ in
        self?.delegate?.buttonClicked()
    }))

    override func viewDidLoad() {
        view.addSubview(button)
    }

    func startLoading() {
        print("show view")
    }
}
```

addTeardownBlockを利用してテストコードを記述します。

```Swift
func testExampleTwo() throws {
    let viewController = LeakingViewControllerTwo()
    let presenter = LeakingViewControllerPresenter(view: viewController)
    viewController.delegate = presenter
    _ = viewController.view
    addTeardownBlock { [weak viewController, weak presenter] in
        XCTAssertNil(viewController, "Object should be deallocated. Detected memory leak.")
        XCTAssertNil(presenter, "Object should be deallocated. Detected memory leak.")
    }
}
```

## XCTestCaseにメモリーリーク検出用拡張機能を追加する。

テストを実行するとテスト対象が一つの時と同じように表示できますが、テスト対象が増えるたびにブロック内のコード量が増えてしまいます。

そこで下記のようにXCTestCaseにextensionを用意してみました。
メッセージの共通化に加えて、ファイルと行を追加して、テストが失敗した正確な行とファイルに失敗メッセージが表示されるようにします。

```Swift
extension XCTestCase {
    func trackForMemoryLeak(instance: AnyObject,
                            file: StaticString = #filePath,
                            line: UInt = #line) {
        addTeardownBlock { [weak instance] in
            XCTAssertNil(
                instance,
                "potential memory leak on \(String(describing: instance))",
                file: file,
                line: line
            )
        }
    }
}
```

実際に利用すると下記のような結果を表示できます。

![Result002](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519522/dc059c68-18c1-97fa-2160-f4ff1bfad336.png)



今回のデモはこちらで公開していますのでご参考になればと思います。

https://github.com/kokiTakashiki/AutomateMemoryLeakDetectionInMySwiftCodeWithXCTest.git

# 参考
https://betterprogramming.pub/preventing-memory-leaks-using-xctests-5fee5e1fa7c5

https://qualitycoding.org/swift-memory-leak-detection-xctest/