Swift 的 `@resultBuilder`;构建自己的HTML DSL解析器,实现简单JSX功能

274 阅读3分钟

🧠 1. 什么是 @resultBuilder

@resultBuilder 是 Swift 提供的一种 自定义 DSL(领域特定语言) 支持工具,允许你用类似“代码块”的语法构造复杂的值结构。
其广泛应用于 SwiftUI(如 @ViewBuilder)、字符串构建器、HTML DSL 等场景。

✅ 你可以将它理解为:

“让多个表达式拼接或组合为一个最终值的机制”。


🧱 2. 基本结构

swift
复制编辑
@resultBuilder
struct MyBuilder {
    static func buildBlock(_ components: T...) -> T
}

其中 T 是你最终组合出的类型,比如 StringView、数组等。


🧩 3. 常用构建方法(方法签名)

一个完整的 resultBuilder 可以实现以下静态方法,支持各种控制流结构:

方法用途
buildBlock(_:)拼接多个表达式的核心方法(必须)
buildExpression(_:)将单个表达式转为构建器的中间值
buildOptional(_:)支持 if letif 条件语句
buildEither(first:) / buildEither(second:)支持 if-else 的两个分支
buildArray(_:)支持 for-in 结构
buildLimitedAvailability(_:)支持 #available 编译条件
buildFinalResult(_:)可选:对最终返回值进行最后处理(Swift 5.9+)

🧪 4. 简单例子:拼接字符串

swift
复制编辑
@resultBuilder
struct StringBuilder {
    static func buildBlock(_ components: String...) -> String {
        components.joined()
    }
}

func makeString(@StringBuilder _ content: () -> String) -> String {
    content()
}

let result = makeString {
    "Hello, "
    "world!"
}
// 输出: "Hello, world!"

🪜 5. 进阶支持:if、for、可选等控制流

swift
复制编辑
@resultBuilder
struct StringBuilder {
    static func buildBlock(_ components: String...) -> String {
        components.joined()
    }

    static func buildOptional(_ component: String?) -> String {
        component ?? ""
    }

    static func buildEither(first: String) -> String {
        first
    }

    static func buildEither(second: String) -> String {
        second
    }

    static func buildArray(_ components: [String]) -> String {
        components.joined(separator: ", ")
    }
}

然后:

swift
复制编辑
makeString {
    "Hello"
    if Bool.random() {
        "🌞"
    } else {
        "🌧️"
    }
    for item in ["A", "B", "C"] {
        item
    }
}

🧮 6. 在 SwiftUI 中的应用:@ViewBuilder

swift
复制编辑
struct MyView: View {
    var body: some View {
        VStack {
            Text("Hello")
            if Bool.random() {
                Text("SwiftUI")
            } else {
                Text("Rocks!")
            }
        }
    }
}

SwiftUI 中的 VStack 使用了 @ViewBuilder,这使得你能用 if、for 等结构直接组合多个视图,而不是手动拼接。


🧑‍💻 7. 自定义 @resultBuilder 应用场景示例

使用场景描述
@ViewBuilderSwiftUI 视图组合
@StringBuilder构建字符串 DSL
@HTMLBuilder构建 HTML DSL
@CommandBuilder构建命令行工具链
@SQLBuilder构建 SQL 语句(如:Swift ORM 框架)

📌 8. Swift 5.9 中的新功能(可选)

buildFinalResult(_:)

这个方法可以对构建器的结果做“最后一轮包装或转换”处理。适用于 Swift 5.9+。


举例,目标:构建 HTML DSL

swift
复制编辑
let html = makeHTML {
    HTML("html") {
        HTML("body") {
            HTML("h1") { "Welcome" }
            HTML("p") { "This is a custom DSL!" }
        }
    }
}

print(html.render())

🧱 第一步:定义 HTML 节点结构

swift
复制编辑
protocol HTMLComponent {
    func render() -> String
}

struct HTMLText: HTMLComponent {
    let text: String
    func render() -> String {
        text
    }
}

struct HTML: HTMLComponent {
    let tag: String
    let children: [HTMLComponent]

    init(_ tag: String, @HTMLBuilder _ content: () -> [HTMLComponent]) {
        self.tag = tag
        self.children = content()
    }

    func render() -> String {
        let inner = children.map { $0.render() }.joined()
        return "<(tag)>(inner)</(tag)>"
    }
}

🧙 第二步:定义 @HTMLBuilder

swift
复制编辑
@resultBuilder
struct HTMLBuilder {
    static func buildBlock(_ components: HTMLComponent...) -> [HTMLComponent] {
        components
    }

    static func buildOptional(_ component: [HTMLComponent]?) -> [HTMLComponent] {
        component ?? []
    }

    static func buildEither(first component: [HTMLComponent]) -> [HTMLComponent] {
        component
    }

    static func buildEither(second component: [HTMLComponent]) -> [HTMLComponent] {
        component
    }

    static func buildArray(_ components: [[HTMLComponent]]) -> [HTMLComponent] {
        components.flatMap { $0 }
    }

    static func buildExpression(_ expression: String) -> [HTMLComponent] {
        [HTMLText(text: expression)]
    }

    static func buildExpression(_ expression: HTMLComponent) -> [HTMLComponent] {
        [expression]
    }
}

🧪 第三步:封装顶层构造器

swift
复制编辑
func makeHTML(@HTMLBuilder content: () -> [HTMLComponent]) -> HTMLComponent {
    let result = content()
    return HTMLText(text: result.map { $0.render() }.joined(separator: "\n"))
}

📦 最终效果

swift
复制编辑
let html = makeHTML {
    HTML("html") {
        HTML("body") {
            HTML("h1") { "Hello Swift!" }
            HTML("p") {
                if Bool.random() {
                    "Dynamic paragraph!"
                } else {
                    "Static paragraph!"
                }
            }
        }
    }
}

print(html.render())

输出可能为:

html
复制编辑
<html><body><h1>Hello Swift!</h1><p>Static paragraph!</p></body></html>

📚 小结

特性说明
灵活组合像 DSL 一样用多个表达式组合出结构
控制流支持 if/else、for、可选绑定等
编译期支持编译器会在语法上理解这些结构
SwiftUISwiftUI 的构建基石
可扩展性强适用于构建任意复杂层级的数据