Can someone explain when and when not to use a \'weak\' assignment to a delegate pointer in Swift, and why?
My understanding is that if you use a protocol that is n
Delegates should (edit: generally) always be weak.
Lets say b
is the delegate of a
. Now a
's delegate
property is b
.
In a case where you want b
to release when c
is gone
If c
holds a strong reference to b
and c
deallocates, you want b
to deallocate with c
. However, using a strong delegate property in a
, b
will never get deallocated since a
is holding on to b
strongly. Using a weak reference, as soon as b
loses the strong reference from c
, b
will dealloc when c
deallocs.
Usually this is the intended behaviour, which is why you would want to use a weak
property.
As Rob said:
It's really a question of "ownership"
That's very true. 'Strong reference cycle' is all about getting that ownership right.
In the following example, we're not using weak var
. Yet both objects will deallocate. Why?
protocol UserViewDelegate: class {
func userDidTap()
}
class Container {
let userView = UserView()
let delegate = Delegate()
init() {
userView.delegate = delegate
}
deinit {
print("container deallocated")
}
}
class UserView {
var delegate: UserViewDelegate?
func mockDelegatecall() {
delegate?.userDidTap()
}
deinit {
print("UserView deallocated")
}
}
class Delegate: UserViewDelegate {
func userDidTap() {
print("userDidTap Delegate callback in separate delegate object")
}
}
Usage:
var container: Container? = Container()
container?.userView.mockDelegatecall()
container = nil // will deallocate both objects
+---------+container +--------+
| |
| |
| |
| |
| |
| |
v v
userView +------------------> delegate
In order to create a strong reference cycle, the cycle needs be complete. delegate
needs to point back to container
but it doesn't. So this isn't an issue. But purely for ownership reasons and as Rob has said here:
In an object hierarchy, a child object should not maintain strong references to the parent object. That is a red flag, indicating a strong reference cycle
So regardless of leaking, still use weak
for your delegate objects.
In the following example, we're not using weak var
. As a result neither of the classes will deallocate.
protocol UserViewDelegate: class {
func userDidTap()
}
class Container: UserViewDelegate {
let userView = UserView()
init() {
userView.delegate = self
}
func userDidTap() {
print("userDidTap Delegate callback by Container itself")
}
deinit {
print("container deallocated")
}
}
class UserView {
var delegate: UserViewDelegate?
func mockDelegatecall() {
delegate?.userDidTap()
}
deinit {
print("UserView deallocated")
}
}
Usage:
var container: Container? = Container()
container?.userView.mockDelegatecall()
container = nil // will NOT deallocate either objects
+--------------------------------------------------+
| |
| |
+ v
container userview
^ |
| |
| |
+------+userView.delegate = self //container+------+
using weak var
will avoid the strong reference cycle
You generally make class protocols (as defined with class
keyword) weak to avoid the risk of a "strong reference cycle" (formerly known as a "retain cycle"). Failure to make the delegate weak doesn't mean that you inherently have a strong reference cycle, but merely that you could have one.
With struct
types, though, the strong reference cycle risk is greatly diminished because struct
types are not "reference" types, so it's harder to create strong reference cycle. But if the delegate object is a class object, then you might want to make the protocol a class protocol and make it weak.
In my opinion, making class delegates weak is only partially to alleviate the risk of a strong reference cycle. It's really a question of "ownership". Most delegate protocols are situations where the object in question has no business claiming ownership over the delegate, but merely where the object in question is providing the ability to inform the delegate of something (or request something of it).