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()