#はじめに
strongやweakなど所有属性を適当につけると、
メモリリークを起こすと、先輩方によく突っ込まれませんか?
どういうことなのか、整理してみました。
よく言われるケースは、
OutletやDelegate、Closuresなどですかね?
SwiftLintでもワーニングが出ます。
今回は、Delegateを例に説明してみます。
サンプルの説明
1st画面、2nd画面、3rd画面の3画面の構成とします。
1st画面の仕様
ボタンを押下すると、2nd画面へ遷移します。
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画面へ遷移します。
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画面へ通知し、画面を閉じます。
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で保持しています。
つまり、循環参照となり、お互いのオブジェクトを参照している状態になっています。
これによりInstrumentsのLeak Checksでモニタリングしても、メモリリークを検出します。
それでは、どうするか?
3rd画面で管理しているdelegateの所有属性をweakにします。
または、2nd画面で管理している3rd画面のインスタンスをweakにします。
これにより循環参照を防止します。
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でモニタリングしても、メモリリークは発生しなくなりました。
まとめ
ARCってよくわからないなという方は、
InstrumentsのLeak Checksでモニタリングしてみると勉強になりそうですよ。
Closuresも考え方は同じです。
該当クラスからクロージャーを参照し、クロージャー内から該当クラスのプロパティを参照しているため、
循環参照が発生し、メモリリークを起こします。
しかしながら、可能であれば変数のスコープを狭くし、ローカル変数で管理すれば、
よりメモリリークを防ぐことができそうですね。