Write Swift macros
Swift & UI 进阶 20m

编写 Swift 宏

Write Swift macros

2023年6月5日

在 Apple 官方观看视频

一句话判断

Swift 5.9 宏的完整指南——从创建你的第一个宏到处理错误诊断,这节课把该讲的都讲了。

这场 Session 讲了什么

Swift 5.9 引入了宏(Macros)系统,允许在编译时自动生成重复代码。Session 详细讲解了宏的工作原理和使用方式。

宏的工作流程:编译器首先检查宏表达式的参数是否匹配宏的参数类型,然后通过编译器插件执行展开。插件将源代码解析为 SwiftSyntax 树,进行任意变换,再将生成的语法树序列化回源代码。整个过程是类型安全的——在展开前就会进行类型检查。

Session 展示了两种宏角色:Freestanding(独立宏,以 # 开头)和 Attached(附加宏,以 @ 开头)。独立宏可以在任何允许表达式的地方使用,附加宏可以增强声明。Xcode 提供了 “Expand Macro” 功能来查看展开结果。

宏的一个重要特性是高度可测试。因为宏是无副作用的确定性变换,使用 assertMacroExpansion 函数编写单元测试是验证宏行为的最佳方式。

值得深挖的点

编译器插件本身就是一个 Swift 程序,可以对语法树进行任意变换。这意味着你可以实现非常复杂的代码生成逻辑。但这也带来了责任——你需要确保生成的代码是正确的。

错误和警告的诊断功能是宏系统中容易被忽视的部分。通过传入的 context 参数,你可以在宏不适用于某个上下文时向编译器报告错误或警告,让用户知道为什么宏无法展开。

代码片段

// 宏声明
@freestanding(expression)
public macro stringify<T>(_ value: T) -> (T, String) =
    #externalMacro(module: "MyMacros", type: "StringifyMacro")

// 宏实现
struct StringifyMacro: ExpressionMacro {
    static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) -> ExprSyntax {
        let argument = node.argumentList.first!
        
        return "(\(argument), \(literal: argument.description))"
    }
}

// 单元测试
func testStringify() {
    assertMacroExpansion(
        "#stringify(a + b)",
        expandedSource: "(a + b, "a + b")",
        macros: ["stringify": StringifyMacro.self]
    )
}

最佳实践

  • 使用 Xcode 的 Swift Macro 模板快速开始
  • 为每个宏编写单元测试,确保展开结果正确
  • 使用 assertMacroExpansion 进行确定性测试
  • 通过 context 向编译器报告有意义的错误和警告
  • 右键点击宏 → “Expand Macro” 查看展开结果

还有什么值得关注

  • Attached 宏可以增强声明(类、属性等)
  • 宏可以用于简化数据模型、网络层等样板代码
  • SwiftSyntax 树是源码精确的结构化表示
  • 宏的泛型支持让它更灵活
WWDC 2023