Skip to main content

Swift Delegate(委派模式)

概述

Delegate 是一種設計模式,常見於 iOS/macOS 開發中。它的核心概念是:

把某個物件的部分行為,交給另一個物件來處理

也就是說:A 物件不自己處理,而是「委派」給 B 物件。

優點

  • 解耦合:避免類別之間強耦合(不需要直接依賴具體的實作)
  • 可重用性:提高程式的可重用性與可維護性
  • 單一職責:遵循單一職責原則(每個物件只專注於自己的核心任務)
  • 靈活性:可以動態改變處理邏輯

常見的 Delegate 範例

在 UIKit / SwiftUI 開發中常見的 Delegate:

  • UITableViewDelegate:控制表格的行為,例如點擊 cell、設定高度
  • UITextFieldDelegate:處理文字輸入框的事件,例如輸入結束、是否允許輸入
  • UIScrollViewDelegate:處理滾動事件
  • CLLocationManagerDelegate:處理位置更新事件

實作架構

Delegate 需要 3 個核心角色

  1. Protocol(協議):定義需要被實作的方法
  2. Delegator(委派者):擁有 delegate 屬性的物件
  3. 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 的情況:

  • 一對多關係:改用 NotificationCenterCombine
  • 簡單回呼:只有單一事件時,Closure 更簡潔
  • 雙向溝通:需要互相呼叫時

Delegate vs Closure 比較

特性DelegateClosure
結構正式、結構化輕量、簡單
方法數量適合多個方法適合單一事件
記憶體管理需要 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)?
// ...
}

最佳實踐

  1. 命名規範:Delegate 協議通常以 Delegate 結尾
  2. 方法命名:使用動詞開頭,如 didTapButtonwillShowAlert
  3. 參數傳遞:將相關資料透過參數傳遞給 delegate
  4. 錯誤處理:定義錯誤處理的 delegate 方法
  5. 文檔註解:為協議方法添加清楚的文檔註解
/// 按鈕管理器的委派協議
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 的使用時機和最佳實踐,能讓你的程式碼更加模組化、可維護且易於測試。