Swift resultBuilder 完全掌握:Builder 模式演进为语言语法的过程
第一次看到 SwiftUI 时,你是否也有过这样的想法?
“等等,只是在 VStack { Text("A"); Text("B") } 大括号中列出多个视图,怎么就能运行?”
第一次看到时确实像魔法。没有分号、逗号,也没有 return,却能自动完成组装。
这种魔法的真面目,就是今天要介绍的 Swift resultBuilder。
先说结论,resultBuilder 把我们过去作为设计模式手动实现的 Builder 模式提升为语言级功能,让编译器代替我们编写。它用一个大括号代码块语法,替代逐步组装对象的重复代码。
Builder 模式到底是什么?
先简单回顾一下传统的 Builder 模式。
Builder 模式不是一次性创建复杂对象,而是逐个添加组件来完成对象。
let request = URLRequestBuilder()
.setURL("https://naver.com")
.setMethod("GET")
.addHeader("Accept", "application/json")
.build()
我们会像这样通过链式调用方法进行组装。
虽然易于阅读,但也有缺点。每次都要手动创建 Builder 类,而且如果忘记 build(),对象就无法完成。
也就是说,开发者必须直接管理组装逻辑。
resultBuilder 正是把这部分工作交给编译器。
resultBuilder 是如何工作的?
核心思路很简单。
编译器会将大括号代码块中列出的值,依次传入预定义的规则函数,最终合并成一个结果。
这些规则函数就是类似 buildBlock 的静态方法。
例如,我们来创建一个收集字符串的简单 builder。
@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 |
先转换每一行的表达式时 |
实现哪些方法,决定了 builder 中允许使用哪些语法。
如果不创建 buildOptional,就无法在代码块中使用 if。
因此,SwiftUI 的 @ViewBuilder 几乎实现了所有这些方法。这样一来,就能在视图代码块中自然使用条件语句和循环。
实际使用后的优点与注意事项
我曾用 resultBuilder 创建小型 HTML 生成器和测试数据。
我最喜欢的一点是,调用位置变得声明式且简洁。由于组装过程被隐藏,读者可以专注于“要创建什么”。
不过,需要注意的地方也很明确。
- 编译错误消息经常不够友好。如果代码块中的类型不匹配,错误可能会指向完全无关的位置。
- 滥用反而会带来问题。如果只是创建一个简单数组,直接使用数组字面量更好。
- 调试时,很难追踪实际调用了哪个
build...方法。
因此,我建议只在 需要反复组装的结构中使用它,例如 DSL(Domain-Specific Language,领域特定语言)。SwiftUI、服务器路由定义和查询 builder 都是例子。
给 iOS 开发实践的一个建议是:通常先理解并熟练使用 @ViewBuilder,比亲自创建 @resultBuilder 更重要。
总结
总结来说,resultBuilder 是语言吸收我们过去手写的 Builder 模式后形成的进化版本。
只要掌握“列在大括号中,编译器就会负责组装”的感觉,就能自然理解 SwiftUI 为什么采用这样的形式。
请亲自输入一遍今天的示例代码。相比只用眼睛阅读,这样能更快掌握。

