『入る学科間違えた高専生』の日記

プログラミングのコードを書いたりする予定です。あとは日記等。あといつまで高専生やねん

Instrumentsを使わずにiOSアプリだけでメモリリークを簡易的に検知する。黒魔術あり

はじめに

Instruments っていう Xcode に同梱されているアプリがあるんですけど、それを使わずに iOS アプリで開発中にメモリリークを検知したいとかあるかなって思って、メモ的なそれとちょっと黒魔術を使っていい感じに実装していくやつ。

環境

  • iOS 13.1 beta 3
  • Swift 5.1
  • Xcode 11 GM Seed
  • macOS Catalina 10.15 beta 8
  • Swift Package Manager

環境自体が Beta 環境なのは許してください。 Xcode 11 GM Seed なので Swift Package Manager (以下 SwiftPM ) を使用してます。
ここで書いていることはすでに Apple から公開されている情報だと思うんですが、もしなにかあれば教えてくださると助かります。

あと今回ライブラリの紹介的な記事になったのでわりと雑に書きます。

使用するライブラリ

github.com

こちらのライブラリが Swift 4.2 実装で SwiftPM に対応済みということなので今回は SwiftPM を使用しています。
Swift4.2 が動く環境であれば、 CocoaPods なり Carthage なり使ってもいいと思います。

セットアップ等

SwiftPM に対応しているライブラリの追加は WWDC 2019 のトークで説明されているのとあと NDA 的に Xcode 11 のスクショをアップロードするのはちょっと怖いので端折らせてもらいます。以下トークのURL。

developer.apple.com

実際簡単に追加できて、例えば Git で Xcode プロジェクト ( or ワークスペース) を管理しているものをローカルにクローンしてきてプロジェクトを開くと勝手にライブラリ群をクローンしてきてくれるのでめっちゃいいなって感じました。

実装

今回は使用するライブラリの README.md の Usage にも書かれてるような感じで実装ができたので、一旦セットアップコードは AppDelegate.swiftapplication(_:didFinishLaunchingWithOptions:) に書いています。

#if DEBUG
DeallocationChecker.shared.setup(with: .alert) // There are other options than .alert too!
#endif

ここで使用した .alert はリーク検知時にどのような形でハンドリングするかっていうものです。

ケース 動作
alert UIAlertController を表示
precondition preconditionFailure を発生
callback DeallocationChecker.Callback に対応した形で動作をカスタマイズ可能

あとは、チェックしたい画面の viewDidDisappear(_:) に以下のように追加します。

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)

    DeallocationChecker.shared.checkDeallocation(of: self)
}

これでメモリリークができるコードを書いておけばいけますね。

import DeallocationChecker
import UIKit

private var retain: ViewController?

final class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        retain = self
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        DeallocationChecker.shared. checkDeallocation(of: self)
    }
}

という感じですね。

でも毎回、 viewDidDisappear(_:) にこれ書くのだるくね?って思った。はい次。

黒魔術入門

Objective-C Runtime Framework って知ってますか?

今回は以下 2 つの Runtime を使います。

developer.apple.com
developer.apple.com

Method Swizzling とも呼ばれるそうです。

UIViewController+Swizzle.swift というファイルを作りました。

import DeallocationChecker
import UIKit

extension UIViewController {

    class func swizzleViewDidDisappear() {
        let from = class_getInstanceMethod(self, #selector(viewDidDisappear(_:)))!
        let to = class_getInstanceMethod(self, #selector(overrideViewDidDisappear(_:)))!
        method_exchangeImplementations(fromMethod, toMethod)
    }

    @objc private func overrideViewDidDisappear(_ animated: Bool) {
        overrideViewDidDisappear(animated)
        #if DEBUG
        DeallocationChecker.shared.checkDeallocation(of: self)
        #endif
    }
}

あとは先程 DeallocationChecker のセットアップコードを書いた下辺りとかに

#if DEBUG
DeallocationChecker.shared.setup(with: .alert) // There are other options than .alert too!
UIViewController.swizzleViewDidDisappear()
#endif

とか書いておいたらいい感じに動くかと思います。

最後に

ちょっとだけ黒魔術を交えて、便利そうなライブラリを使ってみました。
開発中に意図せずメモリリークが発生するコードを書いてしまったときとかに使えそうだなって感じました。
開発しながらはやっぱりスーパー天才エンジニアではない自分は気をつけるのが限界ですが、これを使ったら開発中に気がつけるっていうのが良さげだなって感じました。

この 最後に を書いているときに誰か記事書いてるのかなぁって思ってググってみたら去年の iOSDC Japan 2018 で発表されているようでしたw

speakerdeck.com