Swift Delegate(委派模式)
概述
Delegate 是一種設計模式,常見於 iOS/macOS 開發中。它的核心概念是:
把某個物件的部分行為,交給另一個物件來處理
也就是說:A 物件不自己處理,而是「委派」給 B 物件。
優點
- 解耦合:避免類別之間 強耦合(不需要直接依賴具體的實作)
- 可重用性:提高程式的可重用性與可維護性
- 單一職責:遵循單一職責原則(每個物件只專注於自己的核心任務)
- 靈活性:可以動態改變處理邏輯
常見的 Delegate 範例
在 UIKit / SwiftUI 開發中常見的 Delegate:
UITableViewDelegate:控制表格的行為,例如點擊 cell、設定高度UITextFieldDelegate:處理文字輸入框的事件,例如輸入結束、是否允許輸入UIScrollViewDelegate:處理滾動事件CLLocationManagerDelegate:處理位置更新事件
實作架構
Delegate 需要 3 個核心角色:
- Protocol(協議):定義需要被實作的方法
- Delegator(委派者):擁有 delegate 屬性的物件
- Delegate(代理人):實作協議的物件,負責具體行為
基本實作範例
簡單按鈕管理器範例
// 1. 定義協議
protocol ButtonManagerDelegate: AnyObject {
func didTapButton()
func didLongPressButton()
}
// 2. 擁有 delegate 的物件(Delegator)
class ButtonManager {
weak var delegate: ButtonManagerDelegate? // 用 weak 避免循環引用
func buttonTapped() {
print("ButtonManager: 按鈕被點擊")
delegate?.didTapButton() // 通知代理人
}
func buttonLongPressed() {
print("ButtonManager: 按鈕被長按")
delegate?.didLongPressButton()
}
}
// 3. 代理人,實作協議
class ViewController: ButtonManagerDelegate {
let buttonManager = ButtonManager()
init() {
buttonManager.delegate = self // 設定自己為代理人
}
func didTapButton() {
print("ViewController: 收到點擊通知,處理按鈕點擊!")
}
func didLongPressButton() {
print("ViewController: 收到長按通知,處理長按事件!")
}
}
// 測試
let vc = ViewController()
vc.buttonManager.buttonTapped()
vc.buttonManager.buttonLongPressed()
輸出結果:
ButtonManager: 按鈕被點擊
ViewController: 收到點擊通知,處理按鈕點擊!
ButtonManager: 按鈕被長按
ViewController: 收到長按通知,處理長按事件!
實際 UIKit 範例
UITextFieldDelegate 實作
class LoginViewController: UIViewController {
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// 設定 delegate
usernameTextField.delegate = self
passwordTextField.delegate = self
}
}
// MARK: - UITextFieldDelegate
extension LoginViewController: UITextFieldDelegate {
// 開始編輯時
func textFieldDidBeginEditing(_ textField: UITextField) {
print("開始編輯: \(textField.placeholder ?? "")")
}
// 結束編輯時
func textFieldDidEndEditing(_ textField: UITextField) {
print("結束編輯: \(textField.text ?? "")")
}
// 是否允許開始編輯
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
return true
}
// 是否允許結束編輯
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
return !textField.text?.isEmpty ?? true
}
// 是否允許輸入特定字元
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// 密碼欄位限制長度
if textField == passwordTextField {
let currentText = textField.text ?? ""
let newText = (currentText as NSString).replacingCharacters(in: range, with: string)
return newText.count <= 20
}
return true
}
// 按下 Return 鍵時
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField == usernameTextField {
passwordTextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
// 執行登入邏輯
performLogin()
}
return true
}
private func performLogin() {
// 登入邏輯
print("執行登入...")
}
}
重要注意事項
1. 使用 weak 避免循環引用
// ✅ 正確
weak var delegate: SomeDelegate?
// ❌ 錯誤 - 會造成循環引用
var delegate: SomeDelegate?
2. 可選性檢查
// ✅ 安全呼叫
delegate?.someMethod()
// ❌ 可能崩潰
delegate!.someMethod()
3. 協議繼承 AnyObject
// ✅ 正確 - 限制只能被 class 實作
protocol MyDelegate: AnyObject {
func doSomething()
}
// ❌ 錯誤 - struct 也可以實作,但無法使用 weak
protocol MyDelegate {
func doSomething()
}
使用時機
適合使用 Delegate 的情況:
- 單向溝通:物件之間需要單向事件通知
- 一對一關係:一個物件對應一個處理者
- 多個方法:需要定義多個回呼方法
- 正式結構:需要明確的協議定義
不適合使用 Delegate 的情況:
- 一對多關係:改用
NotificationCenter或Combine - 簡單回呼:只有單一事件時,Closure 更簡潔
- 雙向溝通:需要互相呼叫時
Delegate vs Closure 比較
| 特性 | Delegate | Closure |
|---|---|---|
| 結構 | 正式、結構化 | 輕量、簡單 |
| 方法數量 | 適合多個方法 | 適合單一事件 |
| 記憶體管理 | 需要 weak 避免循環引用 | 需要 weak self 避免循環引用 |
| 可讀性 | 協議定義清楚 | 程式碼更簡潔 |
| 重用性 | 高(協議可被多個類別實作) | 低(通常綁定特定使用場景) |
範例比較
Delegate 方式:
protocol NetworkDelegate: AnyObject {
func didReceiveData(_ data: Data)
func didFailWithError(_ error: Error)
}
class NetworkManager {
weak var delegate: NetworkDelegate?
// ...
}
Closure 方式:
class NetworkManager {
var onSuccess: ((Data) -> Void)?
var onFailure: ((Error) -> Void)?
// ...
}
最佳實踐
- 命名規範:Delegate 協議通常以
Delegate結尾 - 方法命名:使用動詞開頭,如
didTapButton、willShowAlert - 參數傳遞:將相關資料透過參數傳遞給 delegate
- 錯誤處理:定義錯誤處理的 delegate 方法
- 文檔註解:為協議方法添加清楚的文檔註解
/// 按鈕管理器的委派協議
protocol ButtonManagerDelegate: AnyObject {
/// 按鈕被點擊時呼叫
/// - Parameter button: 被點擊的按鈕
func didTapButton(_ button: UIButton)
/// 按鈕長按時呼叫
/// - Parameter button: 被長按的按鈕
/// - Parameter duration: 長按持續時間
func didLongPressButton(_ button: UIButton, duration: TimeInterval)
/// 按鈕操作失敗時呼叫
/// - Parameter error: 錯誤資訊
func didFailWithError(_ error: Error)
}
總結
Delegate 是 iOS 開發中非常重要的設計模式,它提供了物件間通訊的標準化方式。掌握 Delegate 的使用時機和最佳實踐,能讓你的程式碼更加模組化、可維護且易於測試。