Search code examples
iosswiftswift3uinavigationcontrollerretain-cycle

Deallocate view controllers in navigation controller that have a reference to self


Say I have view controllers A, B, C, D & E all embedded in a navigation controller. In view controller B, I have a custom UIImageView object. In C, I have a custom UITextfield object. Both custom classes have a reference to the view controller for various reasons such as I have to perform things like segue when a user taps the image view. To accomplish this, I have this inside each custom class file:

var controller: UIViewController?

And then inside each view controller, inside viewDidLoad I set that variable to self and everything works as expected (segues on tap etc..)

I have an unwind segue from E back to A. However, I noticed that due to these custom objects in view controllers B & C, both were not being deallocated due to a retain cycle caused by having this reference to the view controller. I fixed the issue by setting the controller variable to nil upon segue, however this creates a problem such that if the user goes back (pops the current view controller), because I set the controller variable to nil upon segue, nothing works (it wont segue again because controller var = nil). I thought I might fix this by adding viewWillAppear code as follows:

 override func viewWillAppear(_ animated: Bool) {
    usernameTextField.controller = self
    passwordTextField.controller = self
}

Because I read that viewWillAppear will be called each time the viewcontroller comes into view. This did not fix the problem.

Any ideas on how to go about this? How can I set the controllers to nil during the unwind maybe...?


Solution

  • As the other answers have said you need to make it a weak reference like this:

    weak var controller: UIViewControler?
    

    However I would go further and say that you should not be keeping a reference to to a UIViewController inside any UIView based object (UIImageView, UITextField, etc). The UIViews should not need to know anything about their UIViewControllers.

    Instead you should be using a delegation pattern. This is a basic example:

    1) Create a protocol for the custom UIImageField like this:

    protocol MyImageFieldProtocol: class {
        func imageTapped()
    }
    

    2) Then add a delegate like this:

    weak var delegate: MyImageFieldProtocol?
    

    3) Your UIViewController then conforms to the protocol like this:

    class MyViewController: UIViewController, MyImageFieldProtocol {
    }
    

    4) Somewhere inside the view controller (viewDidLoad is usually a good place you assign the view controller to the image views delegate like this:

    func viewDidLoad {
        super.viewDidLoad()
        myImageView.delegate = self
    }
    

    5) Then add the function to respond to the protocol action to the view controller like this:

    func imageTapped {
        self.performSegue(withIdentifier: "MySegue", sender: nil)
    }