What is the delegation pattern, and why are delegates weak?
TL;DR
Delegation means "I will tell another object when things happen, or ask it what to do."
The contract is a protocol. The delegate property is usually weak so the two objects do not keep each other alive.
UIKit uses it everywhere: UITableViewDelegate, UITextFieldDelegate, UIScrollViewDelegate.
A simple example
protocol DatePickerViewDelegate: AnyObject {
func datePicker(_ picker: DatePickerView, didSelect date: Date)
func datePickerShouldAllowPastDates(_ picker: DatePickerView) -> Bool
}
final class DatePickerView: UIView {
weak var delegate: DatePickerViewDelegate?
private func userTapped(_ date: Date) {
let allowPast = delegate?.datePickerShouldAllowPastDates(self) ?? false
guard allowPast || date >= .now else { return }
delegate?.datePicker(self, didSelect: date)
}
}
final class BookingViewController: UIViewController, DatePickerViewDelegate {
private let picker = DatePickerView()
override func viewDidLoad() {
super.viewDidLoad()
picker.delegate = self
}
func datePicker(_ picker: DatePickerView, didSelect date: Date) {
print("Selected", date)
}
func datePickerShouldAllowPastDates(_ picker: DatePickerView) -> Bool { false }
}Notice two kinds of methods:
- Events: "something happened" (
didSelect). - Questions: "what should I do?" (
shouldAllowPastDates).
Passing self (the sender) as the first argument is a UIKit convention. One delegate can then serve many pickers.
Why weak?
The view controller owns the picker (strong). If the picker also held the controller strongly, neither could be freed.
BookingViewController --strong--> DatePickerView
^ |
+------------ weak -------------+weak only works with classes. So the protocol needs AnyObject:
protocol DatePickerViewDelegate: AnyObject { ... }Optional delegate methods
Swift protocols have no optional methods. Two options:
// 1. Default implementation in an extension (preferred in Swift)
extension DatePickerViewDelegate {
func datePickerShouldAllowPastDates(_ picker: DatePickerView) -> Bool { false }
}
// 2. @objc optional (needed for Objective-C interop)
@objc protocol LegacyDelegate {
@objc optional func didFinish()
}Delegate vs closure vs notification
| Tool | Listeners | Best for |
|---|---|---|
| Delegate | One | Many related callbacks, questions that return values |
| Closure | One | One or two simple callbacks |
| NotificationCenter | Many | App-wide events, loose coupling |
| AsyncSequence / Combine | Many | Streams of values over time |
Closure version:
final class DatePickerView: UIView {
var onSelect: ((Date) -> Void)?
}
picker.onSelect = { [weak self] date in
self?.save(date)
}With closures, you must remember [weak self]. With delegates, the weak is in one place.
Data source is also delegation
UITableViewDataSource asks for data (how many rows, which cell). UITableViewDelegate handles behavior (selection, height). Both are the same pattern.
Common follow-up questions
- Can a delegate be a struct? Not if the property is
weak. Weak needs a class instance. - What if the delegate is
nil? Optional chaining does nothing. Return a safe default for questions. - Is delegation still used in SwiftUI? Rarely. SwiftUI uses closures, bindings and the environment. You still write delegates in a
Coordinatorwhen you wrap UIKit views. - What about Swift 6? UIKit delegates are
@MainActor, so your delegate methods run on the main actor too.