Carlos
Carlos

Reputation: 138

ViewController not being deallocated due to internal retain cycle

Some of my view controllers don't get deallocated after being popped from view. I've gotten rid of the other strong references so I'm left with this internal retain cycle held through a reference form _externalObjectsTableForViewLoading. It's a private UIViewController property so I'm unable to clear it myself. I don't know if iOS has an API to clear it or why it's not being cleared after popping the view controller.

I've tested with with my app running in Release mode both in iOS 11 and 12. Running the app in Instruments renders the same stairs pattern seen in Xcode with the view controllers being retained.

Any ideas? Thanks in advance!

Retain Cycle

Upvotes: 11

Views: 1491

Answers (3)

Pavel Petrenko
Pavel Petrenko

Reputation: 19

The reference of the View Controller to itself through the private property _externalObjectsTableForViewLoading indicates a strong reference cycle. This behavior is caused by using a Storyboard.

In the code below, a controller with a UIStackView is created, inside which a UIView is added with the controller itself as the delegate (strong reference).

class RootViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        let storyboard = UIStoryboard(name: "MyStoryboard", bundle: .main)
        let viewController = storyboard.instantiateViewController(identifier: "MyViewController")
        self.present(viewController, animated: true, completion: nil)
    }
}

protocol MySubviewDelegate {}

class MySubview: UIView {
    let delegate: MySubviewDelegate
    init(delegate: MySubviewDelegate) {
        self.delegate = delegate
        super.init(frame: .zero)
    }
}

class MyViewController: UIViewController, MySubviewDelegate {
    @IBOutlet weak var stackView: UIStackView!
    override func viewDidLoad() {
        super.viewDidLoad()
        let subview = MySubview(delegate: self)
        stackView.addArrangedSubview(subview)
    }
}

The memory graph in this case looks as follows enter image description here

Upvotes: 0

Denis Kutlubaev
Denis Kutlubaev

Reputation: 16114

I had a similar problem. I fixed it using this:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    viewModel?.viewWillDisappear()

    if isMovingFromParent || self.parent?.isBeingDismissed == true {
        viewModel = nil
        tableViewDataSource = nil
        view = nil
    }
}

By removing lines one by one in:

viewModel = nil
tableViewDataSource = nil
view = nil

I found that tableViewDataSource was causing a problem.

To debug deinit of ViewController I used this:

deinit {
    print("[Memory] \(String(describing: self)) deinit")
}

Upvotes: 2

Alex Zavatone
Alex Zavatone

Reputation: 4323

In your problem, is one viewController accessing another viewController? Our problem is that there was a non weak reference to a callback in another viewController.

As mentioned in your and in other posts on this, _externalObjectsTableForViewLoading is a viewController private property, but a storyboard related property. This leads me to think that your code has strong references to another object that is a view controller through a callback or through a direct property reference to its instance.

Upvotes: 2

Related Questions