# Day 13 - Protocols, extensions, and checkpoint 8
## How to create and use protocols
- `protocol` 就像是Swift裡的協定,讓我們定義我們期待一個資料型別支援怎樣的功能,Swift會保證我們剩下的code遵循這些規則
```
protocol Vehicle {
func estimateTime(for distance: Int) -> Int
func travel(distance: Int)
}
```
- 不允許寫function body(protocol不定義如何執行,而比較像是描繪出這種type的藍圖)
- 如果別的type遵循(adopting/conforming)這個protocol,當其function接受的參數或回傳的值不符合protocol內的定義,Swift會報錯
- 但protocol是指出這個type「至少」要具備哪些功能,type本身是允許有其他功能的
```
struct Car: Vehicle {
func estimateTime(for distance: Int) -> Int {
distance / 50
}
func travel(distance: Int) {
print("I'm driving \(distance)km")
}
func openSunroof() {
print("It's a nice day!")
}
}
```
- 這裡的 `openSunroof()` 就是多出來的功能
- 實際使用function
```
func commute(distance:Int, using vehicle: Car) {
if vehicle.estimateTime(for: distance) > 100 {
print("That's too slow! I'll try a different vehicle.")
} else {
vehicle.travel(distance: distance)
}
}
let car = Car()
commute(distance: 100, using: car) // I'm driving 100km
```
- 定義了protocol後,Swift會知道要使用哪個type裡的對應function
- protocol內也可以針對property做協定
```
protocol Vehicle {
var name: String { get }
var currentPassengers: Int { get set }
func estimateTime(for distance: Int) -> Int
func travel(distance: Int)
}
```
- `get`:要可讀
- `set`:要可寫
- 一個type可以遵循多個protocol,如果該type為subclass,則父class寫在冒號後第一個位置,其他依序用逗號隔開
```
struct Car: Vehicle, CanBeElectric {
...
}
```
## How to use opaque return types
- 不透明回傳型別(opaque return types)
- 這個特性雖然複雜又抽象,但在 SwiftUI 專案中會立即出現,因此必須了解其基本概念
- 讓程式可以「隱藏回傳型別的細節」,減少複雜度,同時保留靜態型別檢查的安全性
- example
```
func getRandomNumber() -> Int
func getRandomBool() -> Bool
```
- 這兩個型別(Int、Bool)都遵守 Equatable 協定,可用 `==` 比較
- 嘗試將回傳型別改為 `Equatable`
```
func getRandomNumber() -> Equatable { ... }
```
- 會出現錯誤:「protocol 'Equatable' can only be used as a generic constraint...」
- 原因: `Equatable` 不能單獨作為回傳型別,因為它有「關聯型別 (associated type)」需求
- 雖然 Int 和 Bool 都符合 Equatable,但它們不能互相比較,因此 Swift 不允許直接回傳「某個 Equatable」
- protocol作為回傳型別的用途
- 回傳protocol(例如 Vehicle)可讓我們「隱藏具體型別」而專注於功能
- 這種設計讓程式更靈活:可替換具體類型而不破壞其他程式碼
- 問題本質:為何 Equatable 不適用
- 雖然 Int 和 Bool 都是 Equatable,但它們不可互相比較
- 若直接回傳 Equatable,我們就失去了型別資訊(不知道是 Int 還是 Bool)
- 因此需要一種方式能「對外隱藏型別、對內仍保留真實型別資訊」,這就是不透明回傳型別的作用
- 實際使用:在回傳型別前加上 `some`
```
func getRandomNumber() -> some Equatable
```
- 含義
- 對外:只承諾會回傳「某種符合 Equatable 的型別」
- 對內(編譯器):仍知道實際型別(例如 Int)
- 好處
- 提高彈性:之後可改成回傳 Double 而不影響其他程式碼
- Swift 仍能正確檢查型別安全性
- 「any」 vs 「some」 的概念差異
- `any Vehicle` → 任意符合 Vehicle 協定的型別(呼叫端不知道具體是哪個)
- `some Vehicle` → 某個特定但被隱藏的 Vehicle 型別(編譯器知道是哪個)
→ 「不透明」指的是對使用者隱藏具體型別,但對編譯器不隱藏
- SwiftUI 實際應用
- SwiftUI 的畫面描述極為複雜,理論上 View 的回傳型別會非常長
- 為避免手動寫出完整型別宣告,SwiftUI 使用:
```
var body: some View { ... }
```
- `some View` 告訴 Swift:「這會回傳某種 View,但我不想明確指出它的具體型別;你自己知道就好。」
## How to create and use extensions
- `extension` 讓我們為任何type加上功能性
- example: 去除字串前後的空白及tab等字元
- 一般寫法
```
var quote = " The truth is rarely pure and never simple "
let trimmed = quote.trimmingCharacters(in: .whitespacesAndNewlines)
```
- 用extension達成
```
extension String {
func trimmed() -> String {
self.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
let trimmed = quote.trimmed()
```
- `extension` 的好處
- code自動補全
- code組織性
- 內部存取
- 對於直接改變value本身(而非return一個new value)很有用
```
extension String {
mutating func trim() {
self = self.trimmed()
}
}
```
- 因為動到 `self` 本身,所以要加 `mutating`
- naming convention: 如果會return新的值,function的名字要被取做以 `ed` 或 `ing` 結尾
- ex: `trimmed()`, `reversed()`
- `extension` 在property方面只能新增computed property,不能新增stored property
```
extension String {
var lines: [String] {
self.components(separatedBy: .newlines)
}
}
```
- 將字串從每個新的一行處分開,變成一個陣列
- 結果:
```
let lyrics = """
But I keep cruising
Can't stop, won't stop moving
It's like I got this music in my mind
Saying it's gonna be alright
"""
print(lyrics.lines.count) // 4
```
- 一個 `extension` 實用的use case
- 如果在struct裡直接設定initializer,會導致Swift的memberwise initializer失效,但有時候你會兩種都想要
```
struct Book {
let title: String
let pageCount: Int
let readingHours: Int
}
extension Book {
init(title:String, pageCount: Int) {
self.title = title
self.pageCount = pageCount
self.readingHours = pageCount / 50
}
}
let lotr = Book(title: "Lord of the Rings", pageCount: 1178, readingHours: 24)
let lotrExtInit = Book(title: "Lord of the Rings", pageCount: 1178)
```
- 這樣兩種方式都可以使用
## How to create and use protocol extensions
- 有時候原有的method看起來不直觀,可以用extension做成更直覺的method
```
extension Array {
var isNotEmpty: Bool {
isEmpty == false
}
}
let guests = ["Mario", "Luigi", "Peach"]
if guests.isNotEmpty {
print("Guest count: \(guests.count)") // Guest count: 3
}
```
- protocol extension 讓定義property 或 method變成非必要
```
protocol Person {
var name: String {get}
func sayHello()
}
extension Person {
func sayHello() {
print("Hi, I'm \(name)")
}
}
struct Employee: Person {
let name: String
}
```
- 實例還是可以override extension裡的method,但至少不會因為沒定義就報錯
### Optional: When are protocol extensions useful in Swift?
- Swift中經常使用protocol extension,這就是為什麼有些人會形容Swift是**protocol-oriented programming language**
## Summary: Protocols and extensions
- protocol 就像是我們code的協定,我們寫明某個type需要哪些property或method
- opaque return types讓我們在code裡隱藏某些資訊
- extension 讓我們針對已存在的type新增功能性
- protocol extension讓我們一次針對許多type新增功能性
## Checkpoint 8
### 題目
- 寫一個描述一棟建築的protocol,需符合下列條件
- 有一個property表示這個建築有多少房間
- 有一個property表示這個建築要價多少
- 有一個property表示這個建築的房地產代理
- 有一個method可以印出銷售彙總
- 寫兩個遵循這個protocol的struct: `House` 和 `Office`
### 答案
```
protocol Building {
var rooms: Int { get }
var cost: Int { get set}
var estateAgent: String { get set }
func salesSummary()
}
extension Building {
func salesSummary() {
print("""
rooms: \(rooms)
cost: \(cost)
estate agent: \(estateAgent)
""")
}
}
struct House: Building {
let rooms: Int
var cost: Int
var estateAgent: String
}
struct Office: Building {
let rooms: Int
var cost: Int
var estateAgent: String
}
let house = House(rooms: 3, cost: 10000, estateAgent: "Josh")
let office = Office(rooms: 10, cost: 10000000, estateAgent: "Chang")
house.salesSummary()
office.salesSummary()
// rooms: 3
cost: 10000
estate agent: Josh
rooms: 10
cost: 10000000
estate agent: Chang
```