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 呼叫的瞬間

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


條件式和迴圈如何處理?

有時會想在區塊中使用 iffor,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 為何採用這樣的形式。

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

延伸閱讀