iosNudge

What is the delegation pattern, and why are delegates weak?

EasyAsked oftenUIKitProtocolsMemory & ARC 2 min read

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

ToolListenersBest for
DelegateOneMany related callbacks, questions that return values
ClosureOneOne or two simple callbacks
NotificationCenterManyApp-wide events, loose coupling
AsyncSequence / CombineManyStreams 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 Coordinator when you wrap UIKit views.
  • What about Swift 6? UIKit delegates are @MainActor, so your delegate methods run on the main actor too.