Reputation: 7844
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
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
Reputation: 437792
There are two possible scenarios based upon your code snippet:
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”, , 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:
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.
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
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 print
s 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