Swift 與 Objective-C

Swift resultBuilder 完全攻略:建造者模式演進成語言功能的原因

第一次看到 SwiftUI 時,你是否也有過這樣的想法?

閱讀 4 分鐘
Swift resultBuilder 完全攻略:建造者模式演進成語言功能的原因 封面圖

Swift resultBuilder 完全攻略:建造者模式演進成語言語法的過程

第一次看到 SwiftUI 時,你是否也有過這樣的想法?

「等等,只是在 VStack { Text("A"); Text("B") } 大括號裡列出多個檢視,怎麼就能運作?」

第一次看到時真的很像魔法。沒有分號、逗號,也沒有 return,卻能自動組合起來。

這個魔法的真面目,就是今天要介紹的 Swift resultBuilder。

先說結論,resultBuilder 把我們過去以設計模式手動實作的 建造者模式提升為語言層級的功能,讓編譯器代替我們撰寫。它用一個大括號區塊語法,取代逐步組合物件的重複程式碼。


建造者模式到底是什麼?

先簡單回想一下傳統的建造者模式。

建造者模式不是一次建立複雜物件,而是逐一加入零件來完成物件。

let request = URLRequestBuilder()
    .setURL("https://naver.com")
    .setMethod("GET")
    .addHeader("Accept", "application/json")
    .build()

我們會像這樣串接方法來組合。

雖然容易閱讀,但也有缺點。每次都得手動建立 Builder 類別,若忘了 build(),物件就無法完成。

換句話說,組合邏輯必須由 開發人員直接管理。

resultBuilder 正是把這部分交給編譯器。


resultBuilder 如何運作?

核心概念很簡單。

編譯器會將大括號區塊中列出的值,依序放入預先定義的規則函式,最後合併成單一結果。

這些規則函式就是像 buildBlock 這樣的靜態方法。

例如,來建立一個收集字串的簡單建造者。

@resultBuilder
struct StringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: " ")
    }
}

@StringBuilder
func greeting() -> String {
    "你好"
    "resultBuilder"
    "是"
}
// 結果:「你好 resultBuilder 是"

看看 greeting() 函式內部,我們只是把字串列成三行,對吧?

編譯器會把這三行替換成 buildBlock("안녕하세요", "resultBuilder", "입니다") 呼叫。

區塊列舉轉換成 buildBlock 呼叫的瞬間
區塊列舉轉換成 buildBlock 呼叫的瞬間

也就是說,原本要手動撰寫的組合程式碼,現在由語法代為完成。


條件式和迴圈如何處理?

有時會想在區塊中使用 if 或 for,SwiftUI 中也很常見。

為此,resultBuilder 提供了額外的規則方法。

以下用表格整理主要方法。

方法 何時呼叫
buildBlock 將區塊中的多個值合併為一個值時
buildOptional 只有 if 而沒有 else 時
buildEither(first:) / buildEither(second:) 處理 if-else 分支時
buildArray 收集 for 迴圈結果時
buildExpression 先轉換每一行的表達式時

實作哪些方法,會決定建造者中允許使用哪些語法。

如果不建立 buildOptional,就無法在區塊中使用 if。

因此 SwiftUI 的 @ViewBuilder 幾乎實作了所有這些方法,讓我們能在檢視區塊中自然使用條件式和迴圈。

@ViewBuilder 幾乎實作了所有規則方法,因此才能做到這件事
@ViewBuilder 幾乎實作了所有規則方法,因此才能做到這件事

實際使用後的優點與注意事項

我曾用 resultBuilder 建立小型 HTML 產生器和測試資料。

我最喜歡的是,呼叫端變得宣告式又漂亮。由於看不到組合過程,讀者可以專注在「要建立什麼」。

不過,注意事項也很明確。

  1. 編譯錯誤訊息常常不夠友善。區塊內型別不符時,錯誤位置可能會指向完全無關的地方。
  2. 濫用反而會造成問題。若只是建立一個簡單陣列,直接使用陣列常值更好。
  3. 除錯時,很難追蹤實際呼叫了哪個 build... 方法。

因此,我建議只在 反覆組合的結構這類 DSL(Domain-Specific Language,領域特定語言)中使用,例如 SwiftUI、伺服器路由定義或查詢建造者。

給 iOS 開發實務的一個建議是:比起自己建立 @resultBuilder,先理解並善用 @ViewBuilder 通常更重要。

別只用眼睛閱讀,今天一定要親手輸入一次這個範例
別只用眼睛閱讀,今天一定要親手輸入一次這個範例

總結

總結來說,resultBuilder 是語言吸收我們過去手寫建造者模式後形成的進化版本。

只要掌握「列在大括號裡,編譯器就會負責組合」的感覺,就能自然理解 SwiftUI 為何採用這樣的形式。

今天的範例程式碼請親手輸入一次。這比單純閱讀更快熟悉。

延伸閱讀