Menu
Coddy logo textTech

Delegation Pattern

Part of the Object Oriented Programming section of Coddy's Swift journey. Lesson 46 of 57.

Delegation lets an object hand some decisions to another object, its delegate, through a protocol. The object does its job and asks the delegate at the points that vary. Apple's frameworks use this pattern everywhere:

protocol DownloadDelegate: AnyObject {
    func progress(_ percent: Int)
    func finished(_ file: String)
}

final class Downloader {
    weak var delegate: DownloadDelegate?

    func download(_ file: String) {
        for p in [50, 100] { delegate?.progress(p) }
        delegate?.finished(file)
    }
}

final class Screen: DownloadDelegate {
    func progress(_ percent: Int) { print("\(percent)%") }
    func finished(_ file: String) { print("done: \(file)") }
}

let screen = Screen()
let d = Downloader()
d.delegate = screen
d.download("photo.png")

Output:

50%
100%
done: photo.png

The delegate property is optional and the calls use delegate?., so the object works even when nobody is listening. It is declared weak so the object does not keep its delegate alive by itself, which avoids retain cycles between objects that point at each other:

weak var delegate: DownloadDelegate?   // requires the protocol to be class-only: AnyObject
delegate?.finished(file)               // does nothing when there is no delegate

A delegate can also answer questions, which lets it customise the object's behaviour without subclassing:

protocol ListDelegate: AnyObject {
    func shouldShow(_ item: String) -> Bool
}

final class ListView {
    weak var delegate: ListDelegate?
    func render(_ items: [String]) -> [String] {
        items.filter { delegate?.shouldShow($0) ?? true }
    }
}

final class HideDrafts: ListDelegate {
    func shouldShow(_ item: String) -> Bool { !item.hasPrefix("draft") }
}

let view = ListView()
let rules = HideDrafts()
print(view.render(["a", "draft b"]))
view.delegate = rules
print(view.render(["a", "draft b"]))

Output:

["a", "draft b"]
["a"]

Delegation differs from the observer pattern: an object has one delegate that it asks and informs, while a subject can have many observers that it only informs.

// delegation: one partner, may answer questions
weak var delegate: TableDelegate?
// observer: any number of listeners, notified only
private var observers: [(Event) -> Void] = []
challenge icon

Challenge

Easy

A Checkout asks its delegate for decisions. The protocol CheckoutDelegate (class-only) has discount(for total: Int) -> Int and didComplete(total: Int). Checkout has a weak var delegate, and pay(_ prices: [Int]) -> Int sums the prices, subtracts the delegate's discount (0 without a delegate), tells the delegate the final total and returns it. Implement LoyaltyShop, a delegate that gives 10% off totals of 100 or more and records every completed total in receipts.

The supplied code reads lines of comma-separated prices. It pays the first line without a delegate and the others with a LoyaltyShop, prints each total and finally receipts: 120 90.

Your code goes in CheckoutDelegate.swift, Checkout.swift and LoyaltyShop.swift. main.swift holds the supplied input/output code and cannot be edited.

Try it yourself

// Supplied input/output code: keep it as it is
var input: [String] = []
while let line = readLine() { input.append(line) }
let checkout = Checkout()
let shop = LoyaltyShop()
for (i, line) in input.enumerated() {
    if i == 1 { checkout.delegate = shop }
    print(checkout.pay(line.split(separator: ",").map { Int($0)! }))
}
print("receipts: " + shop.receipts.map { String($0) }.joined(separator: " "))
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Swift playground