函数构建器是在 Swift5.1 中新增的语言特性。它使得 Swift 支持 DSL,有了它,我们可以以一种简洁可读的方式构建各种各样的视图层级。
在这篇文章中,我们将会学习以下内容:
- 什么是函数构建器
- 函数构建器在 Swift 编译器级别是怎么工作的
- 怎样实现自定义的 Swift 函数构建器
理解 Swift 函数构建器
函数构建器是一种利用内置的 DSL 定义的类型,它会从函数中的表达式收集结果,然后把它们组合成返回值。
最小的一个函数构建器类型实现如下:
@_functionBuilder struct Builder {
static func buildBlock(_ partialResults: String...) -> String {
partialResults.reduct("", +)
}
}
函数构建器类型必须用 @functionBuilder 属性声明,这样它才能作为一个自定义属性标签被使用。
buildBlock() 方法是必须实现的,而且必须是 static 的。否则编译器将会报错。
自定义函数构建器标签可以被用在以下地方:
- func, var 和 subscript 声明。函数构建器就会转换被标签修饰的方法体。
- 作为方法参数的闭包,函数构建器将会转换闭包中的内容,除非闭包中包含 return 语句。
在 func, var 和 subscript 中使用 @Builder
// 应用在函数
@Builder func abc() -> String {
"Method:"
"ABC"
}
struct Foo {
// 应用在属性中
@Builder var abc: String {
"Getter:"
"ABC"
}
// 应用在脚本方法中
subscript(_ anything: String) -> String {
@Builder get {
"Subscript:"
"ABC"
}
set { /* nothing*/ }
}
}
结果
// 调用
print(abc())
print(Foo().abc)
print(Foo()[""])
// 打印结果:
Method: ABC
Getter: ABC
Subscript: ABC
在方法闭包参数中使用 @Builder
func acceptBuilder(@Builder build: () -> String) {
print(build())
}
调用:
acceptBuilder {
"Closure argument: "
"ABC"
}
打印结果:
Closure argument: ABC
Swift 函数构建器的目的
Swift 的核心目标是帮助创建优秀的库
函数构建器主要是为了解决:层级数据的构建(译者注:用层级结构的方式描述视图)。例子有:
- 生成结构化数据:XML,JSON
- 生成视图层级:SwiftUI, HTML 那么函数构建器是怎么工作的呢?
Swift 函数构建器的结构
我们把 abc() 方法转换成 AST,结果如下:
(func_decl range=[builder.swift:10:10 - line:13:1] "abc()" interface type='() -> String' access=internal
...
(declref_expr implicit type='(Builder.Type) -> (String...) -> String' location=builder.swift:10:31 range=[builder.swift:10:31 - line:10:31] decl=builder.(file).Builder.buildBlock@builder.swift:5:17 function_ref=single)
...
(string_literal_expr type='String' location=builder.swift:11:5 range=[builder.swift:11:5 - line:11:5] encoding=utf8 value="Method: " builtin_initializer=Swift.(file).String extension.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:) initializer=**NULL**)
(string_literal_expr type='String' location=builder.swift:12:5 range=[builder.swift:12:5 - line:12:5] encoding=utf8 value="ABC" builtin_initializer=Swift.(file).String extension.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:) initializer=**NULL**)
...
我们发现:编译器把 abc() 转换成了对 Builder.builderBlock("Method:", "ABC") 的调用。
在语义分析阶段,编译器把函数构建器的转换应用到解析过的 AST 上,就好像我们直接写的是Builder.buildBlock(<arguments>)似的。
在另一个应用场景方法闭包参数中,编译器会把闭包重写成仅有一条语句的闭包,这条语句就是调用 Builer 的方法。
函数构建器要提供以下方法中的一部分:
buildBlock(_ components: Component...) -> Component把多种可能的结果组合成一个,大部分情况下都是调用这个函数。buildDo(_ components: Component...) -> Component与 buildBlock 类似,但是是用在 do 关键字中的,如果没有声明,将会调用 buildBlock()buildIf(_ components: Component...) -> Component是用在 if 条件语句中的buildEither(first: Component) -> Component与buildEither(second: Component) -> Component配合使用, 对应着 if else 语句。必须两个同时实现。buildOptional(_ component: Component?) -> Component把可选的输入信息,组合成结果