はじめに
Instruments
っていう Xcode に同梱されているアプリがあるんですけど、それを使わずに iOS アプリで開発中にメモリリークを検知したいとかあるかなって思って、メモ的なそれとちょっと黒魔術を使っていい感じに実装していくやつ。
環境
環境自体が Beta 環境なのは許してください。 Xcode 11 GM Seed なので Swift Package Manager
(以下 SwiftPM
) を使用してます。
ここで書いていることはすでに Apple から公開されている情報だと思うんですが、もしなにかあれば教えてくださると助かります。
あと今回ライブラリの紹介的な記事になったのでわりと雑に書きます。
使用するライブラリ
こちらのライブラリが Swift 4.2
実装で SwiftPM に対応済みということなので今回は SwiftPM を使用しています。
Swift4.2 が動く環境であれば、 CocoaPods なり Carthage なり使ってもいいと思います。
セットアップ等
SwiftPM に対応しているライブラリの追加は WWDC 2019 のトークで説明されているのとあと NDA 的に Xcode 11 のスクショをアップロードするのはちょっと怖いので端折らせてもらいます。以下トークのURL。
実際簡単に追加できて、例えば Git で Xcode プロジェクト ( or ワークスペース) を管理しているものをローカルにクローンしてきてプロジェクトを開くと勝手にライブラリ群をクローンしてきてくれるのでめっちゃいいなって感じました。
実装
今回は使用するライブラリの README.md の Usage にも書かれてるような感じで実装ができたので、一旦セットアップコードは AppDelegate.swift
の application(_: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