Swift & UI 进阶 20m
编写 Swift 宏
Write Swift macros
2023年6月5日
一句话判断
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