深入 Swift 宏:角色、展开与实现
Expand on Swift macros
2023年6月5日
一句话判断
Swift 宏不是 C 预处理器的字符串替换,而是一套有类型检查、角色约束和沙盒隔离的编译器插件系统。
这场 Session 讲了什么
Becca 从 Swift 团队的角度系统讲解了 Swift 宏的设计理念和实现机制:
为什么需要宏。Swift 本身有很多自动生成代码的特性(如 Codable、Result Builder),但这些是硬编码在编译器中的。宏让你可以创建自己的”语言特性”,以 Swift Package 的形式分发,不需要修改编译器。
四大设计目标。一看就知道是宏(# 开头的独立宏,@ 开头的附属宏);输入输出都经过类型检查;展开只会添加代码不会删除或修改;不是黑魔法——Xcode 中可以右键查看展开结果、设断点、单步调试。
角色(Role)系统。这是宏的核心约束机制。独立宏有 expression(展开为表达式)和 declaration(展开为声明)两种角色。附属宏有五种:peer(添加新的声明)、accessor(为存储属性添加 getter/setter)、memberAttribute(为成员添加属性)、member(添加新成员)、conformance(添加协议遵循)。
实现机制。宏作为独立的编译器插件运行在安全沙盒中。当编译器遇到宏调用时,提取相关代码发送给插件,插件返回展开后的代码,编译器将其加入程序一起编译。
值得深挖的点
角色决定了展开的插入位置。peer 角色在原声明旁边添加新声明;memberAttribute 为已有成员添加属性标注;member 添加全新的成员;accessor 把存储属性变成计算属性;conformance 添加协议扩展。理解每种角色的规则是正确设计宏的前提。
安全性保障。宏运行在独立进程中,通过沙盒隔离。它只能看到编译器传递给它的语法树片段,不能访问文件系统、网络或进程状态。这种设计让宏的”可添加不可删除”约束得以执行——编译器检查展开结果,确保它只添加了代码。
可调试性。即便宏来自闭源库,你也能在 Xcode 中查看展开结果、设断点、调试。编译错误会同时标注展开代码中的位置和原始代码中的位置。宏作者还可以为宏编写单元测试。
代码片段
// 独立表达式宏:更安全的强制解包
// 声明
@freestanding(expression)
macro assertNotNil<T>(_ value: T?, message: String) -> T
// 使用 — 编译器类型检查确保参数完整
let name: String? = "张三"
let unwrapped = #assertNotNil(name, message: "名字不能为空")
// 展开为类似这样的代码:
// guard let unwrapped = name else {
// fatalError("名字不能为空")
// }
// return unwrapped
// 附属宏:peer 角色示例
// 自动为结构体生成 Equatable 的 == 方法
@attached(peer)
macro AutoEquatable()
// 使用
@AutoEquatable
struct Point {
var x: Double
var y: Double
// 宏会在旁边生成:
// static func == (lhs: Point, rhs: Point) -> Bool {
// lhs.x == rhs.x && lhs.y == rhs.y
// }
}
// 附属宏:member 角色示例
// 为 Codable 结构体自动添加 CodingKeys
@attached(member)
macro AutoCodable()
@AutoCodable
struct User {
var name: String
var age: Int
// 宏自动生成 CodingKeys 枚举和 init(from:) / encode(to:)
}
最佳实践
- 宏的定义和实现要分开:定义提供 API(声明),实现在编译器插件中。
- 选择正确的角色:需要添加新成员用
member,需要修改成员的属性用memberAttribute。 - 为宏编写单元测试,使用 Swift Testing 框架验证展开结果。
- 展开结果要尽量简洁,不要生成过多的中间代码。
- 如果数据可以通过属性或构造器直接传递,就不要用宏——宏适合消除真正的样板代码。
还有什么值得关注
- Xcode 的宏展开查看功能:右键宏调用 -> “Expand Macro”
- Swift Package 中宏的配置需要声明编译器插件 target
- Codable 的自动实现就是一个内置的”宏”概念,宏让这种能力变得可扩展
- 宏的作者要特别注意诊断信息的质量,帮助使用者理解错误