メモリリーク問題でハマったが解決したので、その時のメモ。
※具体的なソースコードを記載したため更新しました。
[この記事の経緯]
SwiftやObjective-Cは、以前のように解放処理を一つ一つのオブジェクトに対して明記しなくても自動的にメモリを解放してくれるようになったが、メモリリークしないような動きに自動的にプログラムしてくれるわけではない。iOSアプリ開発をしていてメモリリークしてしまうケースに遭遇したためこの記事を書きました。
[事象]
UITableViewを動かしていて、deinitをコールしない場合、数十回生成->破棄すると使用中のメモリを逼迫していきOSからKillされる。その際のメモリ使用量はXcodeの左ペイン右から三番目、「Memory Report」から確認。1Gを超えたあたりでクラッシュした。
[前提条件]
deinit関数がコールされないことが原因で画面遷移分のメモリを保持してしまい、メモリリークが起きる。
[今回の対象]
UITableViewが実装されている画面に対して、なんども生成、破棄を繰り返す。
[主な理由]
-
クラスを解放する際にdeinitをコールするが、何らかの理由でコールがされない場合に起こる。
-
ViewController自身のインスタンス(selfなど)を別Viewに代入したままにしている、宣言が"weak"になっていない、もしくは解放時にnilを代入しないままのため、インスタンスが解放されず残存したままとなっている場合にも起こる
-
delegateの宣言が「weak」になっていないため、Viewとのひも付きが解消されず残っているため
以下はdeinit()がコールされない原因がdelegateの宣言によるもの、かつUIViewControllerを変数に保持している場合の解決例
(修正前)
var transitionDelegate: TransitionDelegate! //デリゲートの宣言 "これでは解放されない!"
var owner: UIViewController! //ViewControllerの宣言 "これでは解放されない!"
###(修正後)
class TableViewCell:
UITableViewCell
{
weak var transitionDelegate: (TransitionDelegate & AnyObject)? //デリゲートの宣言 "weakのため解放される"
weak var owner: UIViewController? //ViewControllerの宣言 "weakのため解放される"
deinit
{
debugPrint("TableViewCell called deinit.") //画面遷移の際に呼ばれる
}
}
class ViewController: UIViewController
{
func tapBackView()
{//画面遷移の場合などで破棄処理を行う
var cells = [UITableViewCell]()
for section in 0...numberOfSections-1
{
for row in 0...numberOfRows(inSection: section)
{
if let cell = cellForRow(at: IndexPath(row: row, section: section))
{
cells.append(cell)
}
}
}
for cell: UIView in cells
{
if type(of: cell) == TableViewCell.self
{
let _cell: TableViewCell = cell as! TableViewCell
_cell.transitionDelegate = nil
_cell.owner = nil
}
}
}
}
[修正する際のポイント]
- 原因だと思う変数や関数をコメントアウトして実行し、deinit()がコールされるか、されないかを確認しながら作業を進めると原因が特定しやすく効率が良い
- オーナーシップを確認する。UIViewControllerを宣言する場合はweakとなっているか
- デリゲートの宣言はweakになっているか
- 上記を解放時にnil代入しているか
[所感]
Memory Reportで確認したところ、対処後は画面遷移前後でほぼ同じ値に推移しました。
当該関数は正常に解放された際に呼ばれるため、UIViewControllerやUITableViewなど、それ以外にもカスタムクラスを実装していてメモリリークが発生する場合はコールされているか確認しましょう。
[留意点]
ViewControllerを保持する場合の変数では、weakにすることよって意図しないタイミングで解放されるため、再度参照しないよう注意が必要です。