Lesson 78Protocols

Delegation Pattern

What is Delegation?

Delegation is a design pattern where one object hands off (delegates) some of its responsibilities to another object. It's used extensively in iOS/macOS development!

How It Works

1. Define a protocol with delegate methods
2. Add a weak delegate property to the delegator
3. Call delegate methods when events occur
4. Implement the protocol in another class

Common Use Cases

UI Events

Button taps, text changes, selections

Data Sources

TableView, CollectionView data

Callbacks

Network requests, downloads

Navigation

Screen transitions, dismissals

Why Use weak?

Prevent Retain Cycles!

A (owns) → B
B (delegate) → A
Without weak: A ↔ B = Memory Leak!

Optional Methods

Use protocol extensions to make methods optional:

extension MyDelegate { func optionalMethod() { } }
main.swift
// DELEGATION PATTERN
// One object delegates tasks to another object

// BASIC DELEGATION EXAMPLE
protocol DownloadDelegate: AnyObject {
    func downloadDidStart()
    func downloadDidProgress(_ percent: Double)
    func downloadDidComplete(data: Data)
    func downloadDidFail(error: String)
}

class Downloader {
    weak var delegate: DownloadDelegate?

    func startDownload(url: String) {
        // Notify delegate that download started
        delegate?.downloadDidStart()

        // Simulate download progress
        for i in 1...10 {
            let progress = Double(i) / 10.0
            delegate?.downloadDidProgress(progress)
        }

        // Simulate completion
        let mockData = Data()
        delegate?.downloadDidComplete(data: mockData)
    }
}

class DownloadManager: DownloadDelegate {
    let downloader = Downloader()

    init() {
        downloader.delegate = self
    }

    func download(url: String) {
        downloader.startDownload(url: url)
    }

    // MARK: - DownloadDelegate
    func downloadDidStart() {
        print("Download started...")
    }

    func downloadDidProgress(_ percent: Double) {
        print("Progress: \(Int(percent * 100))%")
    }

    func downloadDidComplete(data: Data) {
        print("Download complete! Size: \(data.count) bytes")
    }

    func downloadDidFail(error: String) {
        print("Download failed: \(error)")
    }
}

// Usage
let manager = DownloadManager()
manager.download(url: "https://example.com/file.zip")

// TABLEVIEW-STYLE DELEGATION
protocol TableViewDataSource: AnyObject {
    func numberOfRows() -> Int
    func cellForRow(at index: Int) -> String
}

protocol TableViewDelegate: AnyObject {
    func didSelectRow(at index: Int)
    func heightForRow(at index: Int) -> Double
}

class SimpleTableView {
    weak var dataSource: TableViewDataSource?
    weak var delegate: TableViewDelegate?

    func reloadData() {
        guard let dataSource = dataSource else { return }

        let count = dataSource.numberOfRows()
        print("Loading \(count) rows:")

        for i in 0..<count {
            let cell = dataSource.cellForRow(at: i)
            print("  Row \(i): \(cell)")
        }
    }

    func selectRow(at index: Int) {
        delegate?.didSelectRow(at: index)
    }
}

class MyViewController: TableViewDataSource, TableViewDelegate {
    let tableView = SimpleTableView()
    let items = ["Apple", "Banana", "Cherry"]

    init() {
        tableView.dataSource = self
        tableView.delegate = self
    }

    // DataSource
    func numberOfRows() -> Int {
        return items.count
    }

    func cellForRow(at index: Int) -> String {
        return items[index]
    }

    // Delegate
    func didSelectRow(at index: Int) {
        print("Selected: \(items[index])")
    }

    func heightForRow(at index: Int) -> Double {
        return 44.0
    }
}

// TEXT FIELD DELEGATION
protocol TextFieldDelegate: AnyObject {
    func textFieldDidBeginEditing(_ textField: CustomTextField)
    func textFieldDidEndEditing(_ textField: CustomTextField)
    func textField(_ textField: CustomTextField, shouldChangeText: String) -> Bool
}

// Optional methods via extension
extension TextFieldDelegate {
    func textFieldDidBeginEditing(_ textField: CustomTextField) { }
    func textFieldDidEndEditing(_ textField: CustomTextField) { }
    func textField(_ textField: CustomTextField, shouldChangeText: String) -> Bool {
        return true  // Allow by default
    }
}

class CustomTextField {
    weak var delegate: TextFieldDelegate?
    var text: String = ""

    func beginEditing() {
        delegate?.textFieldDidBeginEditing(self)
    }

    func endEditing() {
        delegate?.textFieldDidEndEditing(self)
    }

    func updateText(_ newText: String) {
        if delegate?.textField(self, shouldChangeText: newText) ?? true {
            text = newText
        }
    }
}

// FORM VALIDATION EXAMPLE
class FormViewController: TextFieldDelegate {
    let emailField = CustomTextField()
    let passwordField = CustomTextField()

    init() {
        emailField.delegate = self
        passwordField.delegate = self
    }

    func textField(_ textField: CustomTextField, shouldChangeText: String) -> Bool {
        if textField === emailField {
            // Validate email format
            return shouldChangeText.isEmpty || shouldChangeText.contains("@")
        }
        return true
    }

    func textFieldDidEndEditing(_ textField: CustomTextField) {
        print("Field value: \(textField.text)")
    }
}

// WHY USE WEAK FOR DELEGATES?
// To avoid retain cycles!
// Delegator (strong) -> Delegate (weak)
// Otherwise: A -> B -> A = Memory leak!

Try It Yourself!

Create a Timer class with a delegate that gets notified every tick and when the timer completes!