Swift 協定 (Protocols)

協定 (Protocol) 定義了執行某項任務或功能所需的藍圖 (Blueprint)。它規定了「要做什麼」,但不管「怎麼做」。

類別 (Class)、結構 (Struct) 或列舉 (Enum) 可以遵循 (Adopt) 這些協定,並實作具體的邏輯。這類似於 Java 中的 Interface,但功能更強大,是 Swift 「面向協議編程 (Protocol-Oriented Programming)」的核心。

定義協定

使用 protocol 關鍵字。我們可以在協議中定義屬性、方法、建構器等要求。

屬性要求 (Property Requirements)

協議不指定屬性是儲存屬性還是計算屬性,它只指定屬性名稱型別以及讀寫權限 ({ get }{ get set })。

protocol FullyNamed {
    // 必須是可讀寫的 String
    var fullName: String { get set }
    
    // 必須是唯讀的 (但實作者也可以提供可讀寫)
    var description: String { get }
}

方法要求 (Method Requirements)

定義方法時不需要寫出 {} 實作內容。

如果協議是用於 Struct 或 Enum,且方法會修改自身屬性,必須加上 mutating 關鍵字。Class 實作時則不需要寫 mutating。
protocol Togglable {
    mutating func toggle()
}

enum OnOffSwitch: Togglable {
    case off, on
    
    // 因為是 Enum,實作時也必須保留 mutating
    mutating func toggle() {
        switch self {
        case .off: self = .on
        case .on: self = .off
        }
    }
}

建構器要求 (Initializer Requirements)

協議也可以要求實作者必須提供特定的建構器。

protocol SomeProtocol {
    init(someParameter: Int)
}

如果 Class 遵循此協議,實作 init 時必須加上 required 關鍵字,確保子類別也能遵循此協議。

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // ...
    }
}

協定作為型別 (Protocols as Types)

雖然協議本身沒有實作功能,但它可以當作一般的型別來使用。例如:

  • 作為參數和回傳型別。
  • 作為變數、常數或屬性的型別。
  • 作為陣列、字典等集合中的元素型別。
class Dice {
    let sides: Int
    let generator: RandomNumberGenerator // 使用協議作為屬性型別
    
    init(sides: Int, generator: RandomNumberGenerator) {
        self.sides = sides
        self.generator = generator
    }
}

這體現了多型 (Polymorphism) 的特性:Dice 不需要知道 generator 具體是哪個類別,只要它「會產生亂數」就好。

代理模式 (Delegation)

Delegation (代理) 是 iOS 開發中最常見的設計模式。

生活類比:老闆與實習生

想像你是一位 老闆 (Class),你很忙,不想處理瑣碎的雜事 (例如訂便當、接電話)。於是你列了一張 「工作清單」 (Protocol),規定要能幫忙做這些事的人需要具備什麼技能。

任何符合這張清單的人,都可以來當你的 實習生 (Delegate)。當需要訂便當的時候,你只要呼叫實習生去處理就好,你不需要知道他具體是用 UberEats 還是打電話訂的。

程式運作方式

  1. 老闆 (Delegating Object):定義一個 Protocol (工作清單),并持有一個 delegate 變數。
  2. 實習生 (Delegate Object):遵循該 Protocol,並實作具體的邏輯。
// 1. 定義協議 (工作清單)
// 繼承 AnyObject 限制只有 Class 能遵循 (為了稍後使用 weak)
protocol BossAssistantDelegate: AnyObject {
    func didReceivePhoneCall(from number: String)
    func orderLunch() -> String
}

// 2. 老闆 (Delegating Object)
class Boss {
    // 這裡使用 weak 避免循環參考,因為實習生可能也持有老闆的參考
    weak var delegate: BossAssistantDelegate?
    
    func phoneRings(number: String) {
        // 老闆把接電話的工作「外包」給代理人
        delegate?.didReceivePhoneCall(from: number)
    }
    
    func hungry() {
        if let food = delegate?.orderLunch() {
            print("老闆吃到了:\(food)")
        }
    }
}

// 3. 實習生 (Delegate Object)
class Intern: BossAssistantDelegate {
    func didReceivePhoneCall(from number: String) {
        print("實習生:您好,老闆現在不在,請稍後再撥。 (來電: \(number))")
    }
    
    func orderLunch() -> String {
        return "漢堡王"
    }
}

// 4. 實際運作
let myBoss = Boss()
let myIntern = Intern()

myBoss.delegate = myIntern // 聘用實習生

myBoss.phoneRings(number: "0912-345-678") 
// 輸出:實習生:您好,老闆現在不在,請稍後再撥。 (來電: 0912-345-678)

myBoss.hungry()
// 輸出:老闆吃到了:漢堡王

這就是為什麼 UITableView 不知道要顯示什麼資料,它會去問 dataSource (它的代理人);當使用者點擊了某一行,它會通知 delegate (它的代理人) 去處理頁面跳轉。

協定擴展 (Protocol Extensions)

這是 Swift 最強大的特性之一。你可以為 Protocol 提供預設實作!如此一來,遵循協議的型別就不需要重複寫相同的程式碼,只需要實作那些沒有預設實作的部分。

protocol RandomNumberGenerator {
    func random() -> Double
}

extension RandomNumberGenerator {
    // 為 random() 提供預設實作
    func random() -> Double {
        return Double.random(in: 0.0...1.0)
    }
}

協定繼承與合成 (Inheritance & Composition)

繼承

協議可以繼承一個或多個其他協議。

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // 這裡可以增加新的要求
}

合成 (Composition)

如果你需要一個參數同時符合多個協議,可以使用 & 符號 (Protocol Composition)。

func celebrate(to: Named & Happy) {
    // to 必須同時遵循 Named 和 Happy
    print("Happy birthday, \(to.name)!")
}

關聯型別 (Associated Types)

當我們在協議中需要用到「泛型」的概念時,使用的不是 <T>,而是 associatedtype。這在 Swift 的集合型別 (Collection) 中被大量使用。

protocol Container {
    associatedtype Item // 定義一個佔位符名稱
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

struct IntStack: Container {
    // 實作時,Swift 通常能自動推斷 Item 是 Int
    // 也可以明確指定: typealias Item = Int
    var items = [Int]()
    mutating func append(_ item: Int) { items.append(item) }
    var count: Int { items.count }
    subscript(i: Int) -> Int { items[i] }
}

這即是 Swift 強大的 Generic Protocol 基礎。