# Swift 中的 Result builders

By [zzzwco](https://paragraph.com/@zzzwco) · 2022-09-15

---

`2022/08/30` `Xcode 14` `iOS 16` `macOS 13`

在以往的命令式编程中，通常这样构建视图：

    let label = UILabel()
    label.frame = self.view.bounds
    self.view.addSubview(label)
    

但 SwiftUI 采用的是声明式语法：

    VStack {
      Text("1")
      List {
        Text("")
      }
    }
    

显而易见，SwiftUI 比 UIKit 要简洁地多，我们可以轻松地将不同的视图组合成复杂的界面。这得益于 Result builders（结果构造器），它使 SwiftUI 成为了特定领域语言（DSL）。

结果构造器是 Swift 5.4 正式引入的新特性，它可以将一系列子对象组合成新的对象，这个新对象又可以作为子对象去构造更复杂的对象。它在 SwiftUI 中无处不在，如构建场景的 `@SceneBuilder`，构建视图的 `@ViewBuilder`，还有 Swift 5.7 新增的正则构造器 [RegexBuilder](https://github.com/apple/swift-evolution/blob/main/proposals/0351-regex-builder.md) 等。

下面通过一个简单的示例来演示结果构造器的基本使用，假设我们想赋予 UIKit 类似 SwiftUI 的声明式语法，首先声明一个构造器：

    @resultBuilder struct VStackBuilder {
      
      static func buildBlock(_ components: UIView...) -> [UIView] {
        components
      }
    }
    

如上所示，要实现一个结果构造器，需要使用 `@resultBuilder` 修饰，并且必须实现 `buildBlock` 方法。

然后可以使用 `@VStackBuilder` 修饰相应的构造块（build block），如下的 VStack 会从 content 闭包构建视图：

    final class VStack: UIStackView {
      
      @discardableResult
      init(superView: UIView,
           spacing: CGFloat = 10.0,
           @VStackBuilder content: () -> [UIView]) {
        super.init(frame: .zero)
        self.axis = .vertical
        self.spacing = spacing
        self.translatesAutoresizingMaskIntoConstraints = false
        content().forEach { self.addArrangedSubview($0) }
        
        superView.addSubview(self)
        NSLayoutConstraint.activate([
          self.centerXAnchor.constraint(equalTo: superView.centerXAnchor),
          self.centerYAnchor.constraint(equalTo: superView.centerYAnchor),
        ])
      }
      
      required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
      }
    }
    

然后就可以愉快地使用 VStack 了：

    class ViewController: UIViewController {
    
      override func viewDidLoad() {
        super.viewDidLoad()
        
        VStack(superView: self.view) {
          UILabel()
            .text("Hello")
            .textColor(.orange)
          
          UILabel()
            .text("World")
            .textColor(.cyan)
        }
      }
    }
    
    extension UILabel {
      
      @discardableResult
      func text(_ text: String?) -> Self {
        self.text = text
        return self
      }
      
      @discardableResult
      func font(_ font: UIFont) -> Self {
        self.font = font
        return self
      }
      
      @discardableResult
      func textColor(_ textColor: UIColor) -> Self {
        self.textColor = textColor
        return self
      }
    }
    

如果需要在 VStack 中条件渲染视图，只需在相应的 VStackBuilder 中实现相应的静态方法 `buildEither` 即可：

    static func buildBlock(_ component: UIView) -> UIView {
      component
    }
    
    static func buildEither(first component: UIView) -> UIView {
      component
    }
    
    static func buildEither(second component: UIView) -> UIView {
      component
    }
    

然后就可以这样写：

    VStack {
      if Bool.random() {
        UILabel().text("True")
      } else {
        UILabel().text("False")
      }
    }
    

关于结果构造器的更多细节和使用参考：[SE-0289](https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md)。

结果构造器使用简单，但效果神奇！它可以快速地实现 DSL，简化工作。

上例只实现了小部分构件 UI 的功能，并不具备数据绑定和更新状态的能力。此处仅为演示结果构造器的基本用法，并无指导意义。如果你的项目支持的最低版本为 iOS 13，可桥接使用 SwiftUI。

Github 有个仓库收集了许多 Result builders 的用法，参考：[awesome-result-builders](https://github.com/carson-katri/awesome-result-builders)。

---

*Originally published on [zzzwco](https://paragraph.com/@zzzwco/swift-result-builders)*
