Edited at

なぜDelegateをプロパティに持つとweakを指定しなければいけないの?

More than 1 year has passed since last update.


はじめに

strongやweakなど所有属性を適当につけると、

メモリリークを起こすと、先輩方によく突っ込まれませんか?

どういうことなのか、整理してみました。

よく言われるケースは、

OutletやDelegate、Closuresなどですかね?

SwiftLintでもワーニングが出ます。

今回は、Delegateを例に説明してみます。


サンプルの説明

1st画面、2nd画面、3rd画面の3画面の構成とします。


1st画面の仕様

ボタンを押下すると、2nd画面へ遷移します。


FirstViewController.swift

import UIKit

final class FirstViewController: UIViewController {

@IBAction func didTap2ndScene(_ sender: UIButton) {

let secondVC = UIStoryboard.viewController(storyboardName: "Main",
identifier: "SecondViewController")
self.navigationController?.pushViewController(secondVC!, animated: true)

}
}



2nd画面の仕様

3rd画面のインスタンスをプロパティで管理します。

2nd画面から3rd画面のDelegateプロパティに自分自身をセットします。

ボタンを押下すると、3rd画面へ遷移します。


SecondViewController.swift

import UIKit

final class SecondViewController: UIViewController {

var thirdVC: ThirdViewController?

@IBAction func didTap3rdScene(_ sender: UIButton) {

thirdVC = UIStoryboard.viewController(storyboardName: "Main",
identifier: "ThirdViewController")
if let vc = thirdVC {
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
}
}
}

extension SecondViewController: ThirdDelegate {

func completion() {
print(#function)
}
}



3rd画面の仕様

delegateをプロパティで管理します。(所有属性を省略し、strongとします。)

ボタンを押下すると、delegateで2nd画面へ通知し、画面を閉じます。


ThirdViewController.swift

import UIKit

protocol ThirdDelegate: class {
func completion()
}

final class ThirdViewController: UIViewController {

var delegate: ThirdDelegate?

@IBAction func didTapGoBack(_ sender: UIButton) {

delegate?.completion()
self.navigationController?.popViewController(animated: true)
}
}



では、どのタイミングでメモリリークが起こるのでしょうか?

正解は、2nd画面を閉じたタイミングでメモリリークを起こします。

理由としては、2nd画面は、3rd画面のインスタンスをstrongで保持しています。

また、3rd画面は、delegateをstrongで保持しています。

つまり、循環参照となり、お互いのオブジェクトを参照している状態になっています。

スクリーンショット 2017-04-17 17.57.45.png

これによりInstrumentsのLeak Checksでモニタリングしても、メモリリークを検出します。

スクリーンショット 2017-04-17 17.48.58.png


それでは、どうするか?

3rd画面で管理しているdelegateの所有属性をweakにします。

または、2nd画面で管理している3rd画面のインスタンスをweakにします。

これにより循環参照を防止します。

スクリーンショット 2017-04-17 17.59.03.png


ThirdViewController.swift

import UIKit

protocol ThirdDelegate: class {
func completion()
}

final class ThirdViewController: UIViewController {

weak var delegate: ThirdDelegate?

@IBAction func didTapGoBack(_ sender: UIButton) {

delegate?.completion()
self.navigationController?.popViewController(animated: true)
}
}


確認したところ、InstrumentsのLeak Checksでモニタリングしても、メモリリークは発生しなくなりました。

スクリーンショット 2017-04-17 17.50.47.png


まとめ

ARCってよくわからないなという方は、

InstrumentsのLeak Checksでモニタリングしてみると勉強になりそうですよ。

Closuresも考え方は同じです。

該当クラスからクロージャーを参照し、クロージャー内から該当クラスのプロパティを参照しているため、

循環参照が発生し、メモリリークを起こします。

しかしながら、可能であれば変数のスコープを狭くし、ローカル変数で管理すれば、

よりメモリリークを防ぐことができそうですね。