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", "입니다") 呼叫。
也就是說,原本要手動撰寫的組合程式碼,現在由語法代為完成。
條件式和迴圈如何處理?
有時會想在區塊中使用 if 或 for,SwiftUI 中也很常見。
為此,resultBuilder 提供了額外的規則方法。
以下用表格整理主要方法。
| 方法 | 何時呼叫 |
|---|---|
buildBlock |
將區塊中的多個值合併為一個值時 |
buildOptional |
只有 if 而沒有 else 時 |
buildEither(first:) / buildEither(second:) |
處理 if-else 分支時 |
buildArray |
收集 for 迴圈結果時 |
buildExpression |
先轉換每一行的表達式時 |
實作哪些方法,會決定建造者中允許使用哪些語法。
如果不建立 buildOptional,就無法在區塊中使用 if。
因此 SwiftUI 的 @ViewBuilder 幾乎實作了所有這些方法,讓我們能在檢視區塊中自然使用條件式和迴圈。
實際使用後的優點與注意事項
我曾用 resultBuilder 建立小型 HTML 產生器和測試資料。
我最喜歡的是,呼叫端變得宣告式又漂亮。由於看不到組合過程,讀者可以專注在「要建立什麼」。
不過,注意事項也很明確。
- 編譯錯誤訊息常常不夠友善。區塊內型別不符時,錯誤位置可能會指向完全無關的地方。
- 濫用反而會造成問題。若只是建立一個簡單陣列,直接使用陣列常值更好。
- 除錯時,很難追蹤實際呼叫了哪個
build...方法。
因此,我建議只在 反覆組合的結構這類 DSL(Domain-Specific Language,領域特定語言)中使用,例如 SwiftUI、伺服器路由定義或查詢建造者。
給 iOS 開發實務的一個建議是:比起自己建立 @resultBuilder,先理解並善用 @ViewBuilder 通常更重要。
總結
總結來說,resultBuilder 是語言吸收我們過去手寫建造者模式後形成的進化版本。
只要掌握「列在大括號裡,編譯器就會負責組合」的感覺,就能自然理解 SwiftUI 為何採用這樣的形式。
今天的範例程式碼請親手輸入一次。這比單純閱讀更快熟悉。

