3

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?

Rob
  • 415,655
  • 72
  • 787
  • 1,044
Siyuan Ren
  • 7,573
  • 6
  • 47
  • 61

3 Answers3

3

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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
1

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

Community
  • 1
  • 1
Edgar
  • 2,500
  • 19
  • 31
0

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() }

kleinerQ
  • 11
  • 4