普通视图
iOS 实现自定义对象深拷贝(OC/Swift)
Kingfisher图像处理库
在平淡中等待 WWDC 2025 | 肘子的 Swift 周报 #084
在平淡中等待 WWDC 2025 - 肘子的 Swift 周报 #84
不知不觉,距离 WWDC 2025 开始只有 20 天了。在过去的几年中,每当此时我都会写几篇文章畅想 WWDC 上会带来的新功能和我期待的一些变化。然而,或许是因为最近两年 WWDC 上展示的许多新功能并未完全落地,就我个人而言,今年似乎少了往日的热情和渴望。希望这只是我个人的情况。
Swift Macros - 宏替换声明体绑定宏
Swift Macros - 成员属性绑定
Swift Macros - 扩展绑定宏
Swift Macros - 访问器绑定宏
6.4 Swift Macros - 对等绑定宏
在 Swift 宏体系中,PeerMacro
是一种非常灵活且强大的宏协议,专用于生成与绑定声明处于同一作用域的“对等”声明,常用于自动扩展同级的变量、函数或类型定义。
本节将深入介绍 PeerMacro
的用途、定义、参数结构以及实际示例,帮助你理解它在元编程场景中的独特价值。
建议结合《Swift Macros - 宏之全貌》和《Swift Macros - 宏之协议》一并阅读,便于全面理解宏系统的角色协作模型。
1. PeerMacro
的定义
标准库中 PeerMacro
的定义如下:
public protocol PeerMacro: AttachedMacro {
static func expansion(
of node: AttributeSyntax,
attachedTo declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax]
}
这意味着:
- 它是一个 附加宏(attached macro) ;
- 不能生成成员,而是生成与附着声明同级的其他声明;
- 它的返回值为
[DeclSyntax]
,即可以注入多个顶层/局部声明; - 使用范围包括变量、函数、类型、扩展等几乎所有可声明位置。
2. PeerMacro
的典型用途
Peer 宏的应用场景非常广泛,常用于:
场景 | 示例 | 说明 |
---|---|---|
自动生成伴生变量 | @WithWrapper |
为属性生成 _xxx 存储变量 |
自动生成伴生函数 | @BindAction |
为属性自动生成相关行为函数 |
生成衍生声明 | @AutoObservable |
为属性自动生成观察者包装及通知机制 |
声明反射信息 | @Reflectable |
自动生成结构体元信息注册代码 |
特别适合那些需要基于现有声明生成“相关声明”的情境,但不适合直接插入原声明体内的场合。
3. 参数详解
of node: AttributeSyntax
代表宏的语法标记本身,例如 @WithWrapper
。可用于:
- 宏参数提取;
- 判断具体调用语法。
attachedTo declaration: some DeclSyntaxProtocol
- 表示宏附着的原始声明节点;
- 类型是
DeclSyntaxProtocol
,表示可以是变量、函数、类型等; - 你可以从中提取关键元信息(如变量名、类型名、访问级别等)。
in context: some MacroExpansionContext
上下文对象,常用于:
- 生成唯一名称(防止冲突);
- 获取源文件路径、位置;
- 报告诊断信息(如参数错误)。
4. 对等声明的展开位置
Peer 宏生成的声明会插入到与原声明相同的作用域中,而不是类型或函数内部。
例如:
@WithWrapper
var name: String
展开后等同于:
var name: String
private var _name: String = ""
即:_name
是 name
的“对等声明”,它们在同一语法级别上。
5. 示例解析
示例:为变量自动生成属性
用法
struct User {
@DebugEqual
var userName: String = ""
}
// 展开后
struct User {
var userName: String = ""
var debug_userName: String {
"userName = (userName)"
}
}
实现
@attached(peer, names: arbitrary)
public macro DebugEqual() = #externalMacro(module: "McccMacros", type: "DebugEqualMacro")
public struct DebugEqualMacro: PeerMacro {
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// 把通用声明转成变量声明
guard let varDecl = declaration.as(VariableDeclSyntax.self),
// 变量可鞥有多个绑定(var a = 1, b = 2),这里获取第一个。
let binding = varDecl.bindings.first,
// 获取变量名,比如”userName“
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier.text
else {
return []
}
// 生成新的变量名,如 debug_username
// raw: 的作用?原样插入这个标识符文本,不会加引号,也不会逃逸。这是写 Swift 宏时推荐的写法之一。
return [
"""
var debug_(raw: identifier): String {
"(raw: identifier) = \((raw: identifier))"
}
"""
]
}
}
6. 注意事项
-
PeerMacro
会生成多个完整的顶层声明节点,开发者需手动控制命名与作用域; - 若生成的名称不一致,建议配合
names:
标注宏声明; - 生成类型或函数声明时,需手动处理访问修饰符和重名冲突。
7. 总结
PeerMacro
是 Swift 宏系统中“横向扩展”的核心工具,它允许开发者在不修改原始声明的前提下添加紧密关联的辅助声明。适用于:
- 分离逻辑与存储
- 为现有属性扩展行为能力
- 构建声明式属性模型
当你需要构建“围绕声明的附属结构”,PeerMacro
就是你的利器。
Swift Macros - 成员绑定宏
在 Swift 中,结构体和类的声明体(即 {}
中的内容)常常会包含许多重复或模式化的成员声明。为了提升开发效率并避免重复劳动,Swift 宏系统提供了一种用于自动生成成员声明的宏协议:MemberMacro
。在 Swift 宏体系中,MemberMacro
是一种具有极高实用价值的宏协议,它专门用于在类型声明内部生成新的成员(如属性、方法、构造器等)。这种宏是典型的附加宏(attached macro) ,能够大幅减少重复成员定义的样板代码,提高类型声明的表达能力。
本节建议结合《Swift Macros - 宏之全貌》和《Swift Macros - 宏之协议》一并阅读,以便更好地理解宏在声明结构中的角色。
1. MemberMacro
的定义
MemberMacro
是一种 附加宏协议,用于将成员注入至类型声明体中。它只作用于结构体、类、actor、枚举这些具备声明体的类型定义,不能用于函数、变量或其他非类型声明。
它在 Swift 中的声明为:
public protocol MemberMacro: AttachedMacro {
static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax]
}
参数名 | 类型 | 说明 |
---|---|---|
node |
AttributeSyntax |
当前宏调用的语法节点(包含宏名与参数) |
declaration |
some DeclGroupSyntax |
宏所附加的类型声明体,例如 struct 或 class
|
context |
some MacroExpansionContext |
提供诊断、源文件信息等上下文能力 |
你可以通过 MacroExpansionContext
提供的 diagnose()
方法抛出编译错误,也可以用 context.location(of:)
进行精确定位。
返回值为 [DeclSyntax]
,表示你希望宏注入的成员声明数组。例如你可以生成变量、函数、嵌套类型等内容:
return [ "var id: String = UUID().uuidString", "func reset() { self.id = UUID().uuidString }" ]
.map { DeclSyntax(stringLiteral: $0) }
💡 注意:返回的成员会插入到原始类型声明体中,因此要避免命名冲突。
📌 使用限制
- 只可用于具有声明体(
{}
)的类型定义:struct
、class
、enum
、actor
- 不可用于
func
、var
、extension
等其他声明- 若注入的成员包含具名声明(如
var id
),必须在宏声明中通过names:
显式声明,以避免命名未覆盖错误(Declaration name 'id' is not covered by macro
)
2. 使用场景分析
MemberMacro
适用于所有需要自动生成类型成员的场景,特别是:
场景 | 示例 | 说明 |
---|---|---|
自动生成协议实现 | @AutoEquatable |
自动实现 Equatable 的 == 方法 |
自动添加辅助属性 | @Observe |
为属性生成 _xxx 存储与监控 getter |
自动实现构造器 | @AutoInit |
基于属性自动生成初始化函数 |
自动生成默认值 | @WithDefaults |
为成员属性自动附加默认实现 |
3. 示例解析
示例1:AddID
用法:
@AddID
struct User {
var name: String
}
// 等价于
struct User {
var name: String
var id = UUID().uuidString
}
实现:
@attached(member, names: named(id))
public macro AddID() = #externalMacro(
module: "MyMacroImpl",
type: "AddIDMacro"
)
public struct AddIDMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
return [
"var id = UUID().uuidString"
].map { DeclSyntax(stringLiteral: $0) }
}
}
如果不明确名称
@attached(member)
运行会报错:
❗️Declaration name 'id' is not covered by macro 'AddID'
说明你使用的是
@attached(member)
宏,但没有在宏声明中说明要生成的成员名字,Swift 宏系统默认是不允许你偷偷“注入”成员名的,除非你通过names:
明确标注。
示例2:CodableSubclass
对于继承自某个父类的子类,我们希望自动生成 CodingKeys
与 init(from:)
方法.
用法
class BaseModel: Codable {
var name: String = ""
}
@CodableSubclass
class StudentModel: BaseModel {
var age: Int = 0
}
// 宏展开后等效于
class StudentModel: BaseModel {
var age: Int = 0
private enum CodingKeys: String, CodingKey {
case age
}
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.age = try container.decode(Int.self, forKey: .age)
}
}
实现
@attached(member, names: named(init(from:)), named(CodingKeys))
public macro CodableSubclass() = #externalMacro(module: "McccMacros", type: "CodableSubclassMacro")
public struct CodableSubclassMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// 1. 验证是否是类声明
guard let classDecl = declaration.as(ClassDeclSyntax.self) else {
throw MacroError.message("@CodableSubclass 只能用于类")
}
// 2. 验证是否有父类
guard let inheritanceClause = classDecl.inheritanceClause,
inheritanceClause.inheritedTypes.contains(where: { type in
type.type.trimmedDescription == "BaseModel" ||
type.type.trimmedDescription.contains("Codable")
}) else {
throw MacroError.message("@CodableSubclass 需要继承自 Codable 父类")
}
// 3. 收集所有存储属性
let storedProperties = classDecl.memberBlock.members
.compactMap { $0.decl.as(VariableDeclSyntax.self) }
.filter { $0.bindingSpecifier.text == "var" }
.flatMap { $0.bindings }
.compactMap { binding -> String? in
guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else {
return nil
}
return pattern.identifier.text
}
// 4. 生成 CodingKeys 枚举
let codingKeysEnum = try EnumDeclSyntax("private enum CodingKeys: String, CodingKey") {
for property in storedProperties {
"case (raw: property)"
}
}
// 5. 生成 init(from:) 方法
let initializer = try InitializerDeclSyntax("required init(from decoder: Decoder) throws") {
// 调用父类解码器
"try super.init(from: decoder)"
// 创建容器
"let container = try decoder.container(keyedBy: CodingKeys.self)"
// 解码每个属性
for property in storedProperties {
"self.(raw: property) = try container.decode((raw: getTypeName(for: property, in: declaration)).self, forKey: .(raw: property))"
}
}
return [DeclSyntax(codingKeysEnum), DeclSyntax(initializer)]
}
private static func getTypeName(for property: String, in declaration: some DeclGroupSyntax) -> String {
for member in declaration.memberBlock.members {
guard let varDecl = member.decl.as(VariableDeclSyntax.self) else { continue }
for binding in varDecl.bindings {
guard let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self),
identifierPattern.identifier.text == property else {
continue
}
if let typeAnnotation = binding.typeAnnotation {
return typeAnnotation.type.trimmedDescription
}
}
}
// 默认返回 Any,如果找不到匹配
return "Any"
}
}
public enum MacroError: Error, CustomStringConvertible {
case message(String)
public var description: String {
switch self {
case .message(let text):
return text
}
}
}
4. 总结
MemberMacro
是 Swift 宏体系中连接语法结构与声明注入的关键机制。它让开发者能够根据类型结构自动生成成员,真正实现:
- 结构自动扩展;
- 代码样板消除;
- 类型驱动式逻辑推导。
未来你可以将它与 AccessorMacro
、PeerMacro
等组合使用,构建更高层次的声明式元编程能力。
Swift Macros - 声明式独立宏
Swift Macros - 表达式独立宏
Swift Macros - 宏之协议
Swift 宏的强大源于其背后一套精巧严谨的协议体系。这些协议定义了:
- 宏的行为规范:如何与编译器通信,如何生成语法树
- 宏的能力边界:什么宏可以插入什么样的结构
- 宏的输入输出约束:需要接受什么样的输入,返回什么样的输出
在 Swift 中, “宏 = 协议方法的实现” 。宏不会在运行时参与逻辑,而是在编译期间将协议方法转换为结构化代码。
本篇将深入解析这些协议的共性特征与调用方式,为你在后续实现各种角色宏打下统一的基础。
Swift 宏协议的共性特征
Swift 宏虽然分工明确(表达式宏、声明宏、成员宏等),但它们的实现方式高度统一,主要体现为以下特征:
编号 | 特征 | 描述 |
---|---|---|
1 | 方法统一命名为 expansion
|
所有宏协议都实现 static func expansion(...) 作为展开主入口。 |
2 | 支持 throws 异常机制 |
展开过程中可中止并抛出诊断错误。 |
3 | 必带 context 参数 |
提供编译期上下文信息,是宏的“工具箱”。 |
4 | 必带 node 参数 |
表示宏的调用现场,如 #宏名(...) 或 @宏名 。 |
5 | 输入输出皆为 Syntax 类型 |
宏只操作语法树,输入输出都是 SwiftSyntax 节点。 |
6 | 仅在编译期执行 | 宏不能访问运行时信息,所有逻辑基于静态源码。 |
7 | 返回类型严格固定 | 每种宏角色返回类型不同,且不可交叉使用。 |
1. 所有宏都实现 static func expansion(...)
Swift 宏协议统一使用 expansion
方法命名,使得不同类型的宏拥有相似的签名与调用习惯,极大降低学习与维护成本。
// 各协议方法签名示例
protocol ExpressionMacro {
static func expansion(...) throws -> ExprSyntax
}
protocol DeclarationMacro {
static func expansion(...) throws -> [DeclSyntax]
}
- 方法总是
static
,因为宏不依赖实例 - 输入是调用现场
node
+ 编译上下文context
- 输出是结构化语法树,如
ExprSyntax
、DeclSyntax
等
2. 宏支持 throws
,可中止并报告错误
所有宏的 expansion
方法都支持 throws
,允许在发现语义错误时立即中止,并通过 context.diagnose(...)
抛出诊断信息,提升宏的可维护性与用户友好度。
只需要在适当的地方抛出异常,你可以自行编辑异常的message,以便使用者更好的理解该异常。
public struct StringifyMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
throw ASTError("错误提示: the macro does not have any arguments")
}
}
你可以通过自定义错误类型(如 ASTError
)提供清晰的人类可读信息,IDE 也会高亮定位到宏调用位置,提升调试体验。
3. context
宏的工具箱
每个宏都会收到一个 context
参数(类型为 some MacroExpansionContext
),这是宏与编译器交互的主要手段,具备多项能力:
public protocol MacroExpansionContext: AnyObject {
func makeUniqueName(_ name: String) -> TokenSyntax
func diagnose(_ diagnostic: Diagnostic)
func location(of node: some SyntaxProtocol, at position: PositionInSyntaxNode, filePathMode: SourceLocationFilePathMode) -> AbstractSourceLocation?
var lexicalContext: [Syntax] { get }
}
它是宏与编译器沟通的桥梁,也是实现宏逻辑动态化的关键接口。以下是 Swift 宏系统中 MacroExpansionContext
协议四个核心成员的作用详解,按重要性分层说明:
3.1 命名避冲突:makeUniqueName(_:)
自动生成唯一标识符,避免命名冲突
// 使用场景:临时变量、缓存值、内部标识符等场景。
let uniqueVar = context.makeUniqueName("result")
// 输出结果可能是 `result_7FE3A1` 之类的唯一名称
3.2 诊断报告:diagnose(_:)
核心作用:编译时错误报告系统
- 多级诊断:支持 error / warning / note 三种严重级别
- 精准定位:关联到具体语法节点(如高亮错误位置)
- 修复建议:可附加自动修复方案(FixIt)
public struct StringifyMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
context.diagnose(Diagnostic(node: node, message: MacroDiagnostic.deprecatedUsage))
throw ASTError("错误提示: xxxxxx")
}
}
某些宏过期时,可以通过 context.diagnose(...)
给于警告提醒。
DiagnosticMessage
这里的
Diagnostic.message
需要一个实现DiagnosticMessage
协议的实例。public protocol DiagnosticMessage: Sendable { /// The diagnostic message that should be displayed in the client. var message: String { get } /// See ``MessageID``. var diagnosticID: MessageID { get } var severity: DiagnosticSeverity { get } }
message
:诊断信息的信息
diagnosticID
:诊断 ID
severity
:诊断严重程度public enum DiagnosticSeverity { case error // 编译错误,阻止构建。 case warning // 编译警告,不阻止构建。 case note // 提示信息,常用于补充说明。 }
3.3 源码定位:location(of:at:filePathMode:)
可定位到调用宏的具体源代码行列,便于诊断、代码导航、日志标注等用途:
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
let loc = context.location(of: node, at: .afterLeadingTrivia, filePathMode: .fileID )
......
}
func location( of node: some SyntaxProtocol, at position: PositionInSyntaxNode, filePathMode: SourceLocationFilePathMode ) -> AbstractSourceLocation?
从 AbstractSourceLocation 返回值中,可以获取以下信息:
public struct AbstractSourceLocation: Sendable { /// 文件位置 public let file: ExprSyntax /// 行的位置 public let line: ExprSyntax /// 字符位置 public let column: ExprSyntax
-
四种定位模式:
enum PositionInSyntaxNode { case beforeLeadingTrivia // 包含注释/空格 case afterLeadingTrivia // 实际代码起始处 case beforeTrailingTrivia // 实际代码结束处 case afterTrailingTrivia // 包含尾部注释 }
-
路径显示控制:
-
.fileID
→"ModuleName/FileName.swift"
(安全格式) -
.filePath
→ 完整系统路径(调试用)
-
3.4 词法作用域追踪:lexicalContext
核心作用:获取词法作用域上下文
以数组形式,记录从当前节点向外的层层包裹结构;
经过脱敏处理(如移除函数体、清空成员列表)。
// 检查是否在类方法中
let isInClassMethod = context.lexicalContext.contains {
$0.is(FunctionDeclSyntax.self) &&
$0.parent?.is(ClassDeclSyntax.self) != nil
}
4. node
调用现场信息
每个宏的 expansion
方法,除了 context
外,还会接收一个 node
参数,类型通常是 some SyntaxProtocol
(如 FreestandingMacroExpansionSyntax
、AttributeSyntax
等)。
它代表了宏的调用现场——也就是源码中触发宏展开的那段语法结构。
简单理解:
node
就是“#宏名(...)”或“@宏名” 这一整段的解析结果。
以自由宏为例,node
类型通常是 FreestandingMacroExpansionSyntax
,它包含了调用宏时的所有组成元素:
public protocol FreestandingMacroExpansionSyntax: SyntaxProtocol {
var pound: TokenSyntax { get set } // "#" 符号
var macroName: TokenSyntax { get set } // 宏名
var genericArgumentClause: GenericArgumentClauseSyntax? { get set } // 泛型参数
var leftParen: TokenSyntax? { get set } // 左括号 "("
var arguments: LabeledExprListSyntax { get set } // 参数列表
var rightParen: TokenSyntax? { get set } // 右括号 ")"
var trailingClosure: ClosureExprSyntax? { get set } // 尾随闭包
var additionalTrailingClosures: MultipleTrailingClosureElementListSyntax { get set } // 多个尾随闭包
}
具体能做什么?
通过解析 node
,可以在宏内部获取宏调用时传递的信息,从而进行自定义生成:
-
提取参数:解析
arguments
,得到用户传入的内容; -
读取宏名:从
macroName
获取调用者使用的名字(有些宏支持重名扩展); -
处理泛型:如果
genericArgumentClause
存在,可以根据泛型参数生成不同代码; - 解析闭包:支持分析和利用用户传递的尾随闭包;
- 实现自定义行为:比如根据传入参数数量、类型、值,决定生成什么样的代码。
示例
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
// 取出第一个参数
guard let firstArg = node.arguments.first?.expression else {
throw ASTError("缺少参数")
}
// 根据参数生成不同表达式
return "print((firstArg))"
}
小结:
node
= 宏调用时的源码快照,context
= 辅助功能工具箱。
两者结合使用,才能让宏既能理解调用现场,又能灵活地生成对应代码。
5. 输入输出皆基于 Syntax
节点
Swift 宏以结构化 AST(抽象语法树)为基础,输入输出都基于 SwiftSyntax
类型,例如:
- 输入:
AttributeSyntax
、FreestandingMacroExpansionSyntax
、DeclSyntaxProtocol
; - 输出:
ExprSyntax
、[DeclSyntax]
、[AccessorDeclSyntax]
等。
这种设计保证了宏生成的代码具备:
- 与手写代码一致的结构完整性;
- 良好的可分析性与可重构性;
- 自动享受 IDE 语法高亮、错误检测等支持。
Swift 宏不是简单拼接字符串,而是真正生成 AST。
6. 宏只运行于编译时
Swift 宏只能在编译期运行,这意味着它们不能访问运行时信息、全局变量、实例状态或外部服务。所有宏的行为都必须建立在静态源代码、类型系统和语法结构之上。
这为宏提供了如下保证:
- 可预测性:展开结果与运行环境无关,确保行为一致;
- 可分析性:工具链可以分析宏行为,进行语法检查与补全;
- 可维护性:宏代码不会隐藏运行时副作用,有利于重构和测试。
开发者在编写宏时,也应遵循“编译时思维”,尽可能将逻辑转化为静态分析与结构转换。
7. 每种宏的返回类型固定
每个宏协议都明确限定了其 expansion
方法的返回类型,这种限制具有强约束力:
宏协议 | 返回类型 |
---|---|
ExpressionMacro |
ExprSyntax |
DeclarationMacro |
[DeclSyntax] |
MemberMacro |
[DeclSyntax] |
AccessorMacro |
[AccessorDeclSyntax] |
BodyMacro |
[CodeBlockItemSyntax] |
ExtensionMacro |
[ExtensionDeclSyntax] |
MemberAttributeMacro |
[AttributeSyntax] |
这种强约束带来:
- 类型安全;
- 生成结果合法;
- 避免不同宏角色混淆使用。
比如:成员宏只能生成成员声明,不能直接生成表达式或代码块。
总结
Swift 宏协议的结构化设计,使得宏具备了安全、清晰、灵活的特性。无论你编写哪种类型的宏,理解 expansion
的统一调用模式、context
工具箱能力、node
的语法抽象、以及 Syntax
类型的输入输出机制,都是构建可靠宏逻辑的基础。
在接下来的章节中,我们将深入每一种宏协议(如 ExpressionMacro
、DeclarationMacro
等),并结合实际案例,帮助你实现更多有趣且实用的 Swift 宏。
Swift Macros - SwiftSyntax 节点指南
版本:2025.04.27|维护者:Mccc|欢迎共同维护与补充!
在编写 Swift 宏时,你将频繁与 SwiftSyntax
打交道。SwiftSyntax 将源码拆解为结构化的语法节点(Syntax)树,这些节点覆盖了表达式、声明、语句、类型、模式、属性等各个层面。
本篇文章提供一个实用速查表,帮助你快速了解各类常见语法节点的用途与构造方法,便于高效构建宏所需的代码结构。
目录
- 1. 表达式(ExprSyntax)
- 2. 声明(DeclSyntax)
- 3. 语句(StmtSyntax)
- 4. 类型(TypeSyntax)
- 5. 模式(PatternSyntax)
- 6. 属性(AttributeSyntax)
- 7. 宏(MacroExpansionSyntax)
- 8. 其他常用节点
1. 表达式(ExprSyntax)
用于表示各种计算表达式、函数调用、字面量等,是最常见的语法结构之一。
名称 | 描述 | 快速构造示例 |
---|---|---|
ArrayExprSyntax | 数组表达式 [a, b, c]
|
ArrayExprSyntax(elements: [...]) |
BooleanLiteralExprSyntax | 布尔字面量 true / false
|
BooleanLiteralExprSyntax(value: true) |
IntegerLiteralExprSyntax | 整数字面量 123
|
IntegerLiteralExprSyntax(literal: "123") |
FloatLiteralExprSyntax | 浮点字面量 1.23
|
FloatLiteralExprSyntax(floatingDigits: "1.23") |
StringLiteralExprSyntax | 字符串 "abc"
|
StringLiteralExprSyntax(content: "abc") |
IdentifierExprSyntax | 标识符 foo
|
IdentifierExprSyntax(identifier: .identifier("foo")) |
FunctionCallExprSyntax | 函数调用 foo(a, b)
|
FunctionCallExprSyntax(calledExpression: ..., arguments: [...]) |
MemberAccessExprSyntax | 成员访问 a.b
|
MemberAccessExprSyntax(base: ..., name: .identifier("b")) |
PrefixOperatorExprSyntax | 前缀操作 -a
|
PrefixOperatorExprSyntax(operator: "-", expression: ...) |
PostfixOperatorExprSyntax | 后缀操作 a!
|
PostfixOperatorExprSyntax(expression: ...) |
NilLiteralExprSyntax | 空值 nil
|
NilLiteralExprSyntax() |
ClosureExprSyntax | 闭包 { a in a + 1 }
|
ClosureExprSyntax(parameters: ..., statements: [...]) |
TupleExprSyntax | 元组 (a, b)
|
TupleExprSyntax(elements: [...]) |
TryExprSyntax |
try 表达式 |
TryExprSyntax(expression: ...) |
AwaitExprSyntax |
await 表达式 |
AwaitExprSyntax(expression: ...) |
AsExprSyntax | 类型转换 as
|
AsExprSyntax(expression: ..., type: ...) |
IsExprSyntax | 类型检查 is
|
IsExprSyntax(expression: ..., type: ...) |
TernaryExprSyntax | 三目表达式 a ? b : c
|
TernaryExprSyntax(condition: ..., thenExpr: ..., elseExpr: ...) |
SequenceExprSyntax | 表达式序列 1 + 2 * 3
|
SequenceExprSyntax(elements: [...]) |
💡 技巧: 中缀表达式(如
+
,-
,*
)统一由SequenceExprSyntax
表示,不再有 BinaryExpr。
2. 声明(DeclSyntax)
表示变量、函数、类型、协议等的定义,是构建宏时生成结构代码的核心组成。
名称 | 描述 | 快速构造示例 |
---|---|---|
VariableDeclSyntax | 变量 let/var
|
VariableDeclSyntax(bindingSpecifier: "let", bindings: [...]) |
FunctionDeclSyntax | 函数 | FunctionDeclSyntax(name: "foo", signature: ..., body: ...) |
StructDeclSyntax | 结构体 | StructDeclSyntax(identifier: "Foo", memberBlock: ...) |
ClassDeclSyntax | 类 | ClassDeclSyntax(identifier: "Foo", memberBlock: ...) |
EnumDeclSyntax | 枚举 | EnumDeclSyntax(identifier: "Foo", memberBlock: ...) |
ExtensionDeclSyntax | 扩展 | ExtensionDeclSyntax(extendedType: ..., memberBlock: ...) |
ProtocolDeclSyntax | 协议 | ProtocolDeclSyntax(identifier: "Foo", memberBlock: ...) |
ImportDeclSyntax | 导入模块 | ImportDeclSyntax(path: ["Foundation"]) |
TypeAliasDeclSyntax | 类型别名 | TypeAliasDeclSyntax(identifier: "Alias", type: ...) |
AssociatedTypeDeclSyntax | 协议中关联类型 | AssociatedTypeDeclSyntax(identifier: "T") |
MacroDeclSyntax | 宏声明 | MacroDeclSyntax(identifier: "MyMacro") |
OperatorDeclSyntax | 自定义操作符声明 | OperatorDeclSyntax(operatorKeyword: "operator", name: "+") |
3. 语句(StmtSyntax)
用于构建控制流程语句(if、guard、switch 等)和函数体内逻辑结构。
名称 | 描述 | 快速构造示例 |
---|---|---|
IfStmtSyntax | if 语句 | IfStmtSyntax(conditions: [...], body: ...) |
GuardStmtSyntax | guard 语句 | GuardStmtSyntax(conditions: [...], body: ...) |
WhileStmtSyntax | while 循环 | WhileStmtSyntax(conditions: [...], body: ...) |
RepeatWhileStmtSyntax | repeat-while 循环 | RepeatWhileStmtSyntax(body: ..., condition: ...) |
ForStmtSyntax | for-in 循环 | ForStmtSyntax(pattern: ..., inExpr: ..., body: ...) |
SwitchStmtSyntax | switch 分支 | SwitchStmtSyntax(expression: ..., cases: [...]) |
ReturnStmtSyntax | return 返回 | ReturnStmtSyntax(expression: ...) |
ThrowStmtSyntax | 抛出异常 | ThrowStmtSyntax(expression: ...) |
BreakStmtSyntax | break 跳出 | BreakStmtSyntax() |
ContinueStmtSyntax | continue 继续 | ContinueStmtSyntax() |
DeferStmtSyntax | defer 延后执行 | DeferStmtSyntax(body: ...) |
4. 类型(TypeSyntax)
用于表示类型声明,包括简单类型、数组、可选、元组、函数类型等。
名称 | 描述 | 快速构造示例 |
---|---|---|
SimpleTypeIdentifierSyntax | 基本类型 Int, String
|
SimpleTypeIdentifierSyntax(name: "Int") |
OptionalTypeSyntax | 可选类型 Int?
|
OptionalTypeSyntax(wrappedType: ...) |
ArrayTypeSyntax | 数组类型 [Int]
|
ArrayTypeSyntax(elementType: ...) |
DictionaryTypeSyntax | 字典类型 [K: V]
|
DictionaryTypeSyntax(keyType: ..., valueType: ...) |
TupleTypeSyntax | 元组类型 (Int, String)
|
TupleTypeSyntax(elements: [...]) |
FunctionTypeSyntax | 函数类型 (Int) -> Bool
|
FunctionTypeSyntax(parameters: [...], returnType: ...) |
AttributedTypeSyntax | 带属性类型 @Sendable
|
AttributedTypeSyntax(attributes: [...], baseType: ...) |
SomeTypeSyntax |
some 类型 |
SomeTypeSyntax(baseType: ...) |
MetatypeTypeSyntax |
.Type .Protocol
|
MetatypeTypeSyntax(baseType: ..., typeOrProtocol: ...) |
ExistentialTypeSyntax |
any 协议类型 |
ExistentialTypeSyntax(type: ...) |
5. 模式(PatternSyntax)
用于 let/var
绑定、模式匹配等结构。
名称 | 描述 | 快速构造示例 |
---|---|---|
IdentifierPatternSyntax | 标识符模式 | IdentifierPatternSyntax(identifier: .identifier("name")) |
TuplePatternSyntax | 元组模式 | TuplePatternSyntax(elements: [...]) |
WildcardPatternSyntax | 通配符 _
|
WildcardPatternSyntax() |
ValueBindingPatternSyntax | let/var 模式 | ValueBindingPatternSyntax(bindingSpecifier: "let", pattern: ...) |
ExpressionPatternSyntax | 表达式匹配 | ExpressionPatternSyntax(expression: ...) |
6. 属性(AttributeSyntax)
用于修饰声明,包括标准属性和自定义属性包装器。
名称 | 描述 | 快速构造示例 |
---|---|---|
AttributeSyntax | 标准属性 | AttributeSyntax(attributeName: "available") |
CustomAttributeSyntax | 自定义属性 | CustomAttributeSyntax(attributeName: "MyWrapper") |
7. 宏(MacroExpansionSyntax)
专门用于表示宏的使用与展开。
名称 | 描述 | 快速构造示例 |
---|---|---|
FreestandingMacroExpansionSyntax | 表达式独立宏 #stringify(x)
|
FreestandingMacroExpansionSyntax(macroName: "stringify", arguments: [...]) |
AttributeMacroExpansionSyntax | 属性宏 @MyMacro
|
AttributeMacroExpansionSyntax(macroName: "MyMacro", arguments: [...]) |
AccessorMacroExpansionSyntax | Accessor 宏(getter/setter) | AccessorMacroExpansionSyntax(macroName: "MyAccessor") |
8. 其他常用节点
名称 | 描述 | 快速构造示例 |
---|---|---|
CodeBlockSyntax | 一组语句块 { ... }
|
CodeBlockSyntax(statements: [...]) |
MemberDeclListSyntax | 成员声明列表 | MemberDeclListSyntax(members: [...]) |
ParameterClauseSyntax | 参数签名 (x: Int)
|
ParameterClauseSyntax(parameters: [...]) |
TupleExprElementListSyntax | 元组表达式元素列表 | TupleExprElementListSyntax(elements: [...]) |
TokenSyntax | 基础 Token,如标识符/关键字等 |
.identifier("foo") , .keyword(.func)
|
SourceFileSyntax | 整个 Swift 源文件语法结构 | SourceFileSyntax(statements: [...]) |
来源
为了确保内容的准确性和时效性,欢迎您定期参考官方文档和资源:
如有更新,提交MR,一起维护它。
Swift Macros - 宏之语法树
Swift Macros - 宏角色与命名控制
Swift Macros - 宏之全貌
1. 宏的定义
Swift 宏(Macro) 是一种在编译期执行的代码生成机制。开发者可以通过简洁的语法标记,在源代码中自动插入、替换或补充逻辑,从而实现样板代码的自动化。
Swift 宏建立在语法树与类型系统之上,具备类型安全、语义明确与可预测的元编程特性。
为什么使用宏?
Swift 宏的优势体现在以下几个方面:
- 编译期执行,零运行时开销 宏在编译阶段完成代码展开,避免运行时反射或动态调用的性能负担。
-
减少样板代码,提升开发效率 无需手动实现
Equatable
、Codable
、监听器等重复性逻辑,宏可以自动生成这些代码。 - 类型安全,语法无缝衔接 宏展开后的代码与手写代码一样,会经历完整的语法与类型验证,确保可靠性与一致性。
2. 宏的设计原则
Swift 宏的设计秉承“显式、安全、可预测”三大原则,避免“魔法式”的隐式行为:
原则 | 说明 |
---|---|
显式调用 | 宏必须通过明确语法标记使用,开发者清晰可见。 |
类型检查 | 宏生成的代码会经过完整的类型系统验证,不会绕过语言规则。 |
可预测展开 | 宏的展开逻辑必须是稳定的、可预期的,结果不会因外部环境而改变。 |
宏不是魔法,它并不神秘,也不凌驾于语言规则之上。你写下的每一个宏调用,都将以可读、可测、可调试的方式插入源代码中。
3. 宏的原理
Swift 宏基于编译器插件(Compiler Plug-in) 机制运行,整个过程发生在编译期,并受到严格的沙盒限制。
宏的展开流程
- 提取宏调用:编译器识别源码中的宏语法,并生成对应的原始语法树(Raw Syntax Tree)。
- 发送到宏插件:宏语法树被发送至对应插件,该插件在沙盒中以独立进程运行。
- 执行宏逻辑:插件处理语法树并生成新的代码片段(语法节点)。
- 插入并继续编译:新生成的语法节点被插入原始源码,参与后续的编译过程。
宏的安全性与纯粹性
为了确保宏系统的 安全、稳定与可控性,Swift 从两个维度对宏行为做出约束:
系统隔离:沙盒机制
所有宏插件运行在独立的沙盒进程中,Swift 对其能力进行了严格限制:
- ✖️ 禁止访问文件系统(如
FileManager
) - ✖️ 禁止发起网络请求
- ✖️ 禁止调用系统级 API
这些限制是编译器层面的强制规定,一旦访问受限资源,会立即报错,例如:
"The file “xxx” couldn’t be opened because you don’t have permission to view it."
因此,即使使用第三方宏插件,也无需担心其在背后执行未授权的操作。
设计哲学:纯粹性原则
Swift 鼓励将宏视为纯函数 —— 相同输入始终生成相同输出。这有助于:
- 提高宏行为的可预测性
- 避免构建结果因环境不同而变化
- 支持编译器缓存宏结果,提升性能
推荐做法
- ✔️ 仅依赖编译器传入的语法树与上下文
- ✔️ 避免访问系统环境、网络、文件
- ✔️ 生成稳定、可测、可重现的代码
不建议行为
- ✖️ 使用
UUID()
或Date()
等生成动态值 - ✖️ 使用随机数作为默认值
- ✖️ 在多个宏之间共享全局上下文或隐式状态
这些行为虽然 技术上允许,但会破坏宏的一致性,导致难以调试、不可复现的构建结果。
4. 宏角色与命名说明符:Swift 宏的职责与命名控制
Swift 宏并非千篇一律,它具备明确的职责划分,这种职责由编译器通过一套称为 宏角色(Macro Role) 的机制识别和执行。
4.1 宏角色:Swift 宏的功能标识
宏角色决定了一个宏可以做什么。Swift 中的宏主要分为两类:
-
独立宏(Freestanding) :使用
@freestanding(...)
标记,独立于任何已有声明,适合生成表达式或新的声明语句。 -
绑定宏(Attached) :使用
@attached(...)
标记,附着在已有声明(如类型、函数、属性)上,用于扩展或修改它们的结构。
每种宏角色都对应特定的协议,定义其展开行为:
宏角色 | 描述 | 协议名 | 示例用途 |
---|---|---|---|
@freestanding(expression) |
表达式独立宏 | ExpressionMacro |
替换或扩展表达式 |
@freestanding(declaration) |
声明式独立宏 | DeclarationMacro |
添加变量、函数、类型声明 |
@attached(member) |
成员绑定宏 | MemberMacro |
向类型中注入属性、方法等成员 |
@attached(peer) |
对等绑定宏 | PeerMacro |
在声明旁插入并列的新声明 |
@attached(accessor) |
访问器绑定宏 | AccessorMacro |
自动生成 get/set 等属性访问器 |
@attached(extension) |
扩展绑定宏 | ExtensionMacro |
生成扩展(extension) |
@attached(memberAttribute) |
成员属性绑定宏 | MemberAttributeMacro |
修改成员的注解、属性等 |
@attached(body) |
函数体替换绑定宏 | BodyMacro |
替换计算属性或函数的实现体 |
独立宏以
#宏名(...)
使用,绑定宏以@宏名(...)
使用。
这些角色为 Swift 宏构建起了清晰的职责体系 —— 每个宏角色都对应一类语法结构的生成或修改行为。
4.2 命名说明符:绑定宏中的命名控制器
对于会生成 具名实体(如属性、函数、类型) 的宏,Swift 提供了另一套机制来进一步控制“生成出来的东西叫什么”,这就是 命名说明符(Name Specifier) 。
在绑定宏(例如 MemberMacro
、AccessorMacro
)中,我们通常使用 expanded
方法返回字符串形式的声明代码。但如果不明确命名,编译器将视这些内容为 匿名生成,从而带来几个问题:
- 无法在语义层面识别生成成员的名称;
- 代码补全、跳转、文档工具支持不佳;
- 多个宏同时生成代码时容易发生命名冲突;
- 其他宏无法可靠地引用这些生成内容。
为了解决这些问题,Swift 引入了 命名说明符 机制,用于精确指定宏生成的实体名称。例如:
@attached(extension, names: named(==))
这表示:宏将生成一个具名为 ==
的成员方法。
命名说明符的种类与用途
命名说明符 | 典型用途 | 原始声明 | 宏生成结果 |
---|---|---|---|
named("...") |
设定固定名称 | struct MyView {} |
static func makePreview() |
prefixed("...") |
给生成成员加前缀 | var name: String |
var debug_name: String |
suffixed("...") |
给生成成员加后缀 | func save() |
func saveAsync() |
overloaded |
添加重载版本 | func log() |
func log(level: LogLevel) |
arbitrary |
自定义命名(复杂场景) | struct User {} |
_UserFlagsHelper , internalMap 等 |
5. 宏协议:决定宏行为的功能接口
Swift 宏的功能是建立在一套明确分层的协议体系上的。这些协议定义了宏的 基本行为、适用场景,以及 如何响应编译器的宏展开请求。
5.1 宏的基础协议:Macro
所有 Swift 宏都遵循 Macro
协议,它是宏体系的根基,定义了宏的基本能力和默认行为。
public protocol Macro {
/// 控制宏展开后的代码是否格式化,默认为 `.auto`
static var formatMode: FormatMode { get }
}
-
.auto
(默认):使用格式化后的展开代码,推荐使用,能保持代码一致性。 -
.disabled
:展开后的代码将原样插入,不进行格式化,适用于自定义输出。
5.2 宏的分类协议:FreestandingMacro
与 AttachedMacro
在 Macro
协议的基础上,Swift 将宏分为两类:
-
FreestandingMacro
:用于 独立使用的宏,可以直接插入到表达式、声明等任何地方,适合用来生成简单的表达式。
public protocol FreestandingMacro: Macro { }
-
AttachedMacro
:用于 附着在已有代码上的宏,必须绑定到已有的类型、属性、函数等声明上,适合对已有代码进行扩展。
public protocol AttachedMacro: Macro { }
这两个协议本身不定义任何具体行为,它们为更细分的角色协议提供了基础。
💡 Swift 使用协议体系来设计宏的目的是:
- 层次清晰:基础协议定义宏的公共行为,高层协议划分宏的使用场景,角色协议定义宏的具体能力。
- 编译器驱动:根据宏的角色和位置,编译器调用特定协议中的
expansion(...)
方法展开宏。- 类型安全:协议方法的定义明确,展开时处理的语法结构与上下文类型都有严格的检查。
5.3 宏的角色协议
每个宏的角色都需要实现一个静态方法 expansion(of:in:)
,这是编译器在宏展开时调用的核心方法。该方法将接收当前语法节点和上下文信息,并返回生成的语法树,最终插入到用户代码中。
💡 一个宏的实现可以遵循多个协议,从而具备多重角色能力。 例如,以下
AutoCodableMacro
同时实现了MemberMacro
和AccessorMacro
,因此它具备生成成员和访问器的能力:
public struct AutoCodableMacro: MemberMacro, AccessorMacro {
public static func expansion(...) -> [DeclSyntax] { ... }
public static func expansion(...) -> [AccessorDeclSyntax] { ... }
}
这正是 Swift 宏系统的强大之处 —— 通过协议组合实现宏的 多重角色。
5.4 主要宏角色协议
接下来,我们将一一解析不同的角色协议,详细说明每个协议的职责、调用时机及适用场景。
1. 表达式独立宏:ExpressionMacro
public protocol ExpressionMacro: FreestandingMacro {
static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax
}
功能:插入和替换表达式。适用于动态计算、生成常量、包装表达式等。
2. 声明式独立宏:DeclarationMacro
public protocol DeclarationMacro: FreestandingMacro {
static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax]
}
功能:用于插入新的声明(例如,变量、函数、类型声明等)。
3. 对等绑定宏:PeerMacro
public protocol PeerMacro: AttachedMacro {
static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax]
}
功能:在现有声明旁边生成平级结构,通常用于插入同级声明。
4. 访问器绑定宏:AccessorMacro
public protocol AccessorMacro: AttachedMacro {
static func expansion(
of node: AttributeSyntax,
providingAccessorsOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [AccessorDeclSyntax]
}
功能:为属性添加访问器(如 get
、set
、didSet
等)。
5. 成员属性修饰宏:MemberAttributeMacro
public protocol MemberAttributeMacro: AttachedMacro {
static func expansion(
of node: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
providingAttributesFor member: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [AttributeSyntax]
}
功能:为成员添加统一的修饰符或属性标签。
6. 成员绑定宏:MemberMacro
public protocol MemberMacro: AttachedMacro {
static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax]
static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [DeclSyntax]
}
功能:为类型添加成员(如属性、方法、构造器等)。
7. 替换声明体绑定宏:BodyMacro
public protocol BodyMacro: AttachedMacro {
static func expansion(
of node: AttributeSyntax,
providingBodyFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax,
in context: some MacroExpansionContext
) throws -> [CodeBlockItemSyntax]
}
功能:为现有声明提供具体实现或行为,常用于生成计算属性的实现或补充函数体。
8. 扩展绑定宏:ExtensionMacro
public protocol ExtensionMacro: AttachedMacro {
static func expansion(
of node: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [ExtensionDeclSyntax]
}
功能:为类型生成扩展,通常用于协议一致性等。
9. 未公开使用的实验宏
CodeItemMacro
:用于插入宽泛的代码片段。 PreambleMacro
:为文件自动注入文件级代码。
6. 宏的结构设计:从角色到行为的思维路径
Swift 宏系统之所以强大,在于它并不追求“全能型”宏,而是通过“角色划分”将每种宏限制在特定场景中。这不仅让系统具备类型安全与上下文约束,还帮助开发者在设计宏时建立起清晰的思维路径。
本章我们将构建这样一个模型:每一种宏的“角色” → 应该遵循的“协议” → 实现的“行为结构” 。
并通过一些实际例子,帮助你理解 如何基于宏的使用意图,选择正确的协议与输出结构。
6.1 宏角色简析:你要扩展什么?
一个宏所作用的语法位置被称为它的 角色(Role) 。角色决定了宏能应用在哪类语法结构上(如表达式、属性、类型、函数体等),也决定了宏展开时能生成哪类代码结构。
你想做什么? | 角色名称 | 示例 |
---|---|---|
在表达式中插入代码? | 表达式级宏 | #stringify(value) |
为 struct 自动添加成员? | 成员绑定宏 | @AddID |
生成 computed 属性的 getter/setter? | 属性访问宏 | @UserDefault |
自动生成某个函数体? | 函数体宏 | @AddDescription |
为类型生成协议扩展和默认实现? | 扩展绑定宏 | @CodableSubclass |
额外添加旁路函数或类型? | 对等绑定宏 | @BindEvent |
每个角色背后都对应着一个(或多个)专用协议,用来限制其行为。
6.2 协议是角色的具象化
Swift 宏协议是以 Macro
结尾的一组协议,定义了你在该角色下应该实现的接口。
角色 | 对应协议 | 你要返回的结构类型 |
---|---|---|
表达式级宏 | ExpressionMacro |
ExprSyntax |
声明级宏 | DeclarationMacro |
[DeclSyntax] |
成员绑定宏 | MemberMacro |
[DeclSyntax] |
对等绑定宏 | PeerMacro |
[DeclSyntax] |
属性访问宏 | AccessorMacro |
[AccessorDeclSyntax] |
扩展绑定宏 | ExtensionMacro |
[ExtensionDeclSyntax] |
成员属性宏 | MemberAttributeMacro |
[AttributeSyntax] |
函数体宏 | BodyMacro |
CodeBlockSyntax |
这些协议都提供了一个 static func expansion(...)
方法,但根据角色不同,返回的语法结构也各不相同。
6.3 建立宏的设计思维路径
宏的本质是 “你想让它为你生成什么代码?” ,这套设计过程可以简化为三步:
你想扩展的目标(角色)
↓
确定宏协议
↓
实现 expansion,构造语法树(行为)
我们将这个过程称为**「角色 → 协议 → 行为」**的思维模型。
例子 1:我想为 struct 添加一个成员 ID
- ⛳ 目标:为 struct 添加成员
- 🎭 角色:成员绑定宏(struct 的成员)
- 📜 协议:
MemberMacro
- 🔧 行为:返回
DeclSyntax
形式的变量声明
@AddID
struct User { }
→ 展开为:
struct User {
var id: String = UUID().uuidString
}
例子 2:我想为属性自动生成访问器(getter/setter)
- ⛳ 目标:替属性添加访问器
- 🎭 角色:属性访问宏
- 📜 协议:
AccessorMacro
- 🔧 行为:返回
[AccessorDeclSyntax]
,如get
和set
@UserDefault("age")
var age: Int
→ 展开为:
get { UserDefaults.standard.integer(forKey: "age") }
set { UserDefaults.standard.set(newValue, forKey: "age") }
例子 3:我想自动为某个函数生成实现体
- ⛳ 目标:添加函数体
- 🎭 角色:函数体宏
- 📜 协议:
BodyMacro
- 🔧 行为:返回
CodeBlockSyntax
@AddDescription
func description() -> String
→ 展开为:
{
return "name=(self.name), age=(self.age)"
}