Swift retain cycle explanation

i0S Swift Issue

Question or problem in the Swift programming language:

Here is my custom view:

class CustomVIew: UIView {

    deinit {
        print("custom view deinit")
    }

    var onTapViewHandler: (()->Void)?
}

and the View Controller:

class ViewControllerB: UIViewController {

    var customView: CustomVIew!

    deinit {
        print("B deinit")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let customView = CustomVIew()
        customView.onTapViewHandler = { [unowned self] in
            self.didTapBlue()
        }
        customView.frame = CGRect(x: 50, y: 250, width: 200, height: 100)
        customView.backgroundColor = UIColor.blueColor()
        view.addSubview(customView)

        self.customView = customView

    }

    func didTapBlue() {

    }
}

When the controller is popped from the navigation stack, everything is fine:

B deinit
custom view deinit

But when I replace this code:

customView.onTapViewHandler = { [unowned self] in
     self.didTapBlue()
}

with this:

 customView.onTapViewHandler = didTapBlue

then, nothing is printed on the console. The CustomView and ViewController are not released, why?

Why does customView.onTapViewHandler = didTapBlue capture a reference to self?

How to solve the problem:

Solution 1:

Swift function is a type of closure. So like closure(Block in objective c) does functions can capture references.

when customView.onTapViewHandler = didTapBlue gets executes a reference to self ie ViewControllerB reference in this case will be captured by the function call.

Same time ViewControllerB‘s view holds strong reference to CustomVIew so it makes retain cycle.

About using unowned, Apple document says:


Weak and unowned references enable one instance in a reference cycle
to refer to the other instance without keeping a strong hold on it.
The instances can then refer to each other without creating a strong
reference cycle.

That means no circular reference and retain cycle.

Solution 2:

If you add the [unowned self] capture list to the closure, the view holds a weak reference to self, and self holds a strong reference to the view.

Since nothing has a strong reference to self, self can be deinitialized when the view controller is popped. After self is deinitialized, nothing has a strong reference to the view anymore, so it is deinitialized too.

If you remove the capture list, self holds a strong reference to the view and the view holds a strong reference to self. This means that in order to deinitialize self, the view must be deinitialized first (which will break the strong reference). But in order for the view to be deinitialized, the view controller must be deinitialized first to break the strong reference to the view. But you can’t break the strong reference to the view unless you deinitialize the view controller. You can’t break the strong reference to the view controller unless you deinitialize the view.

See? We have gotten into an infinite loop here! So neither the view nor the view controller will be deinitialized!

Hope this helps!