Why should a delegate property be weak, and why must the protocol be class-bound?
Predict first
Does this compile?
protocol Delegate {}
final class Worker {
weak var delegate: Delegate?
}
- Yes
- No
Predict first
What does this print with a strong delegate property?
protocol Delegate: AnyObject {}
final class Worker { var delegate: Delegate? }
final class Screen: Delegate {
let worker = Worker()
init() { worker.delegate = self }
deinit { print("screen gone") }
}
var s: Screen? = Screen()
s = nil
print("after")
- screen gone, after
- after
Mental model. A news channel reports to its audience but does not own them. If it did, the audience could never leave.
A delegate is usually the owner of the object that calls it: a screen owns its data service, and the service reports back to the screen. If the service held the screen strongly too, the two would form a cycle. So the convention is weak var delegate: SomeDelegate?: the delegate is notified, but not owned.
A weak reference needs a class type, so the protocol must be class-bound: protocol SomeDelegate: AnyObject. Without that the compiler rejects weak and tells you to add a class bound. The exception is a delegate you intend to own (an URLSession holds its delegate strongly until invalidated, by design), which should be documented because it is a deliberate cycle that you break with invalidateAndCancel.
A protocol that is not class-bound could be adopted by a struct, which cannot be weak. Declare protocol Delegate: AnyObject.
The screen owns the worker and the worker owns the screen: a cycle. Making delegate weak lets screen gone print.
Common traps
- Forgetting
AnyObjectand then removingweakto make it compile. You silently create the cycle. - Making the delegate property
let. Delegates change, so keep a settable weak variable.
They might follow up with
- Which framework delegates are strong?
URLSessionretains its delegate until the session is invalidated; read the docs for each. - How does this change with closures? A closure callback has the same shape; the same rule applies through a capture list.
Related questions
- 3.2.2 Why is a weak reference optional, what can be weak, and what is the trap when reading one?
- 3.3.3 When does a closure create a cycle, and why doesn’t a lazy var closure always?
← What is a retain cycle, and how do you find and fix one? When does a closure create a cycle, and why doesn’t a lazy var… →