Siyuan Ren
Siyuan Ren

Reputation: 7844

Is method closure retaining the instance in swift?

In swift, I can use instance methods as closures, for example, assigning the method to a callback

self.someView.someCallback = self.doSomething

So, is self strongly referenced here in self.doSomething? Does the line above create a reference loop?

Upvotes: 3

Views: 960

Answers (3)

kleinerQ
kleinerQ

Reputation: 11

In Swift, declare a closure type variable, and would like to assign a func to it, prevent from the retain issue, just do as follow, search the answer for all day long, eager to share:

self.someView.someCallback = { [unowned self] in self.doSomething() }

Upvotes: 0

Rob
Rob

Reputation: 437792

There are two possible scenarios based upon your code snippet:

  1. If doSomething is a instance method of self, then, yes, that line establishes a strong reference. Remember that Closures are Reference Types. You can easily confirm this and is easily confirmed empirically. Consider:

    class ViewController: UIViewController {
    
        var foo: (() -> Void)?
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            foo = bar
    
            foo?()
        }
    
        func bar() { ... }
    }
    

    If I present and dismiss this view controller three times, and then use Xcode’s “Debug Memory Graph”, debug memory graph, I will see those three instances still lingering in memory (on the left) and if I select one, it will show me the strong reference cycle visually in the center panel:

    enter image description here

    And because I used the “Malloc stack” feature, on the right panel I can see precisely where the lingering strong reference is, namely in viewDidLoad where I set that closure.

  2. However, if doSomething is not a function, but rather is a closure, then that line, itself, does not establish a strong reference, but rather it becomes a question of whether the closure, itself, refers to self and, if it does, whether there is a [weak self] or [unowned self] capture list or not. For more information, see Strong Reference Cycles for Closures.

Upvotes: 4

Edgar
Edgar

Reputation: 2540

In order to have a retain cycle, you need to have a strong reference on each direction, i.e.:

Object A strongly references Object B

Object B strongly references Object A

Assuming self in the code you shared is a View Controller, and assuming someView is a strong reference to a view, we could say that:

Object A (View Controller) strongly references Object B (Some View)

Now if Object B (Some View) has a strong reference back to the View Controller, you will have a retain cycle.

Assuming doSomething is a method in your ViewController, and not a closure, you will have a retain cycle

An easy way to check this, is by implementing deinit in both your Some View and your View Controller, like so:

class SecondViewController: UIViewController {

    var someView: CustomView?

    override func viewDidLoad() {
        super.viewDidLoad()

        someView = CustomView(frame: view.frame)
        someView?.someCallback = doSomething
    }

    func doSomething() {
    }

    deinit {
        print(#function)
    }
}

final class CustomView: UIView {
    var someCallback: (() -> Void)?

    deinit {
        print(#function)
    }
}

You will see that the prints on deinit are never printed out in the console. However changing the way you assign someCallback to:

someView?.someCallback = { [weak self] in
    self?.doSomething()
}

will cause deinit to run, thus breaking the retain cycle

Edit:

Or even, as an alternative:

weak var weakSelf = self
someView?.someCallback = weakSelf?.doSomething

(Even though this is using a weak reference, because this expression is evaluated at the time the assignment of someCallback is performed, not at the time it is executed, this will still become a strong reference) - Thanks @Rob

Upvotes: 1

Related Questions