---
# System prepended metadata

title: 'Day 13 - Protocols, extensions, and checkpoint 8'

---

# 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
```