Swift Function Builder

1,260 阅读2分钟

Swift @functionBuilder

SwiftUI 允许我们写出这样的代码:

VStack {
    Text("Line 1")
    Spacer().frame(height: 16)
    Text("Line 2")
}

首先 VStack { /* ... */ } 实际是初始化了一个 VStack。这个初始化函数的最后一个参数是一个闭包,这个闭包需要返回 View 类型的对象。

这是它的定义:

/// A view that arranges its children in a vertical line.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@frozen public struct VStack<Content> : View where Content : View {

    @inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)

    public typealias Body = Never
}

这里有两个疑点:

  • 闭包的类型是 () -> Content,而 Contentwhere 从句限制为服从 View 协议的类型,因此可以认为闭包 content 返回的应是 some View,而我们却返回了三个 View 对象。
  • 而且就算要返回多个 View,按照经验,我们可以选择元组或其他一些容器对象,而此处我们却直接写在了 { /* 这里面 */ },甚至中间连个逗号都没有

看来看去也就这个 @ViewBuilder 最为可疑了,查看它的定义:

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@_functionBuilder public struct ViewBuilder {

    /// Builds an empty view from an block containing no statements, `{ }`.
    public static func buildBlock() -> EmptyView

    /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) through
    /// unmodified.
    public static func buildBlock<Content>(_ content: Content) -> Content where Content : View
}

它是一个 @_functionBuilder 标注的 struct,仿照它的格式,我尝试写了一个 TensorBuilder

@_functionBuilder struct TensorBuilder {
    static func buildBlock<T>(_ tensors: T...) -> [T] {
        tensors
    }
}

然后将它加到 Tensor 的初始化函数中去(Tensor 只不过是 Array 的 alias 而已):

typealias Tensor = Array

extension Tensor {
    init(@TensorBuilder _ builder: () -> Self) {
        self = builder()
    }
}

TensorBuilder 的功能就是把输入的元素“串”起来,比如将三个标量变成一个向量:

let vector = Tensor {
    0
    1
    2
}

// [0, 1, 2]

将两个向量组成矩阵:

let matrix = Tensor {
    vector
    [3,4,5]
}

// [[0, 1, 2], [3, 4, 5]]

将两个矩阵组成张量:

let tensor = Tensor {
    matrix
    Tensor {
        [6,7,8]
        [9,10,11]
    }
}

// [[[0, 1, 2], [3, 4, 5]], [[6, 7, 8], [9, 10, 11]]]

后面还能组成阶数更高的张量。

这个功能算是处于半正式公开的状态,能用,但是前面还有个 _

我的这个例子比较没意思(也没啥用),这里有很多别人写的 @_functionBuilderawesome-function-builders

比如 HTML-DSL

html(lang: "en-US") {
  body(customData: ["hello":"world"]) {
    article(classes: "readme", "modern") {
      h1 {
        "Hello World"
      }
    }
  }
}

比如 NSAttributedStringBuilder

NSAttributedString {
  AText("Hello world")
    .font(.systemFont(ofSize: 24))
    .foregroundColor(.red)
  LineBreak()
  AText("with Swift")
     .font(.systemFont(ofSize: 20))
     .foregroundColor(.orange)
}