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!