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)
定義方法時不需要寫出 {} 實作內容。
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 還是打電話訂的。
程式運作方式
- 老闆 (Delegating Object):定義一個 Protocol (工作清單),并持有一個
delegate變數。 - 實習生 (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 基礎。