LoginSignup
36
40

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-04-17

はじめに

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も考え方は同じです。
該当クラスからクロージャーを参照し、クロージャー内から該当クラスのプロパティを参照しているため、
循環参照が発生し、メモリリークを起こします。

しかしながら、可能であれば変数のスコープを狭くし、ローカル変数で管理すれば、
よりメモリリークを防ぐことができそうですね。

36
40
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
40