一、宏(Macros)
通过宏在编译时生成代码
宏(Macros)允许你在编译期间转换源代码,从而避免手动编写重复代码。在编译过程中,Swift 会在常规代码构建前展开所有宏,生成实际代码。
宏展开始终是一种增补操作:宏会添加新代码,但绝不会删除或修改现有代码。
宏的输入(原始代码)和宏展开后的输出(生成代码)均会经过检查,以确保它们是语法有效的 Swift 代码。同样,传递给宏的值以及宏生成代码中的值也会被检查,以确保类型正确。此外,如果宏的实现(在展开过程中)遇到错误,编译器会将其视为编译错误。这些保障机制使得:使用宏的代码更易于理解和推理;更容易识别问题(例如错误使用宏,或宏实现本身存在缺陷)。
二、宏的类型
Swift 有两种类型的宏:
- 独立宏(Freestanding Macros):独立存在,不附加在任何声明上。
- 附着宏(Attached Macros):修改它们所附加的声明。
2.1、Freestanding Macros
要调用独立宏,你需要在宏名称前加上井号(#),并在名称后的括号内传入参数。例如:
let c = #stringify(20)
那么如何定义一个宏呢?
@freestanding(expression)
public macro stringify<T>(_ value: T) -> String = #externalMacro(module: "MyMacroMacros", type: "StringifyMacro")
@freestanding(expression)
public macro Intstringify<T>(_ value: T) -> String = #externalMacro(module: "MyMacroMacros", type: "Intstringify")
@freestanding(expression)
public macro PublicStruct(_ name: String) = #externalMacro(module: "MyMacroMacros", type: "StringifyMacro")
-
@freestanding(expression)
:表明这是一个独立宏,属于表达式类别。 -
public macro stringify<T>(_ value: T) -> String、public macro Intstringify<T>(_ value: T) -> String、 public macro PublicStruct(_ name: String)
这里就是宏的语法表达结构 -
#externalMacro(module: "MyMacroMacros", type: "StringifyMacro")
,module:指定宏实现所属模块,需与 Package.swift 中的模块名一致;type:实现该宏的具体类型,需继承自 ExpressionMacro 协议。
在大多数 Swift 代码中,当你实现一个符号(例如函数或类型)时,并不需要单独的声明。然而对于宏而言,其声明和实现是分离的。
下面看一下宏的实现:
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
///
public struct StringifyMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> ExprSyntax {
guard let argument = node.arguments.first?.expression else {
fatalError("compiler bug: the macro does not have any arguments")
}
return "\(literal: argument.description)"
}
}
每个宏角色需遵循特定协议以实现代码生成逻辑,例如:
ExpressionMacro → @freestanding(expression)
DeclarationMacro → @freestanding(declaration)
PeerMacro → @attached(peer)
AccessorMacro → @attached(accessor)
MemberMacro → @attached(member)
具体实现时,需通过 swift-syntax 库解析语法树(AST),并在 expansion 方法中生成代码。
补充
独立宏以 # 开头,无需依赖其他声明,直接生成代码。其角色包括:
- 表达式宏 @freestanding(expression)
生成一个表达式,并返回计算结果。例如 #stringify(a + b) 会被展开为 (a + b, "a + b")13。 - 声明宏 @freestanding(declaration)
生成新的声明(如函数、结构体等)。例如 #myStruct 可能生成一个包含特定属性和方法的 struct15。
2.2、Attached Macros
要调用一个附加宏(Attached Macro),你需在其名称前添加 @ 符号,并将宏>的参数写在名称后的圆括号内。
附加宏的作用是修改它们所附加的声明,通过向该声明添加代码来实现功能扩展,例如:
定义新方法
添加对协议的遵循(Conformance)
调用附加宏的方式与作用
示例说明
假设有以下未使用宏的代码:
struct User {
var name: String
var age: Int
}
通过附加宏(如 @Codable),可为该结构体自动生成 Codable 协议的编解码实现代码,无需手动编写。
@Codable // 附加宏自动生成 Codable 协议实现
struct User {
var name: String
var age: Int
}
宏的定义和实现
import SwiftSyntaxMacros
/// 定义 `@Codable` 附加宏,自动生成 `Codable` 协议实现
@attached(
member, // 角色:添加成员
names: named(CodingKeys), // 生成的符号名称
named(init(from:)),
named(encode(to:))
)
public macro Codable() = #externalMacro(
module: "MyMacros", // 宏实现的模块名
type: "CodableMacro" // 宏实现的具体类型
)
import SwiftSyntax
import SwiftSyntaxMacros
import SwiftSyntaxBuilder
public struct CodableMacro: MemberMacro {
/// 核心方法:解析声明并生成代码
public static func expansion(
of node: AttributeSyntax, // 宏属性(如 `@Codable`)
providingMembersOf declaration: some DeclGroupSyntax, // 附加的声明(如结构体)
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// 1. 验证附加声明是否为结构体或类
guard let structDecl = declaration.as(StructDeclSyntax.self) ?? declaration.as(ClassDeclSyntax.self) else {
throw MacroError.message("`@Codable` 只能用于结构体或类")
}
// 2. 提取所有存储属性(Stored Properties)
let storedProperties = structDecl.memberBlock.members
.compactMap { $0.decl.as(VariableDeclSyntax.self) }
.filter { $0.isStoredProperty }
.flatMap { $0.bindings }
// 3. 生成 CodingKeys 枚举
let codingKeysEnum = try generateCodingKeysEnum(for: storedProperties)
// 4. 生成 init(from:) 和 encode(to:) 方法
let initMethod = try generateDecoderInitMethod(for: storedProperties)
let encodeMethod = try generateEncoderMethod(for: storedProperties)
// 5. 返回生成的代码
return [codingKeysEnum, initMethod, encodeMethod].map { DeclSyntax($0) }
}
// 生成 CodingKeys 枚举的私有方法
private static func generateCodingKeysEnum(for properties: [PatternBindingSyntax]) throws -> EnumDeclSyntax {
let cases = properties.map { property in
let name = property.pattern.trimmedDescription
return "case \(raw: name)"
}
return try EnumDeclSyntax("enum CodingKeys: String, CodingKey { \(raw: cases.joined(separator: "\n")) }")
}
// 生成 init(from:) 方法的私有方法
private static func generateDecoderInitMethod(for properties: [PatternBindingSyntax]) throws -> InitializerDeclSyntax {
let container = "let container = try decoder.container(keyedBy: CodingKeys.self)"
let assignments = properties.map { property in
let name = property.pattern.trimmedDescription
return "\(name) = try container.decode(\(property.type!).self, forKey: .\(name))"
}
return try InitializerDeclSyntax("""
init(from decoder: Decoder) throws {
\(raw: container)
\(raw: assignments.joined(separator: "\n"))
}
""")
}
// 生成 encode(to:) 方法的私有方法
private static func generateEncoderMethod(for properties: [PatternBindingSyntax]) throws -> FunctionDeclSyntax {
let container = "var container = encoder.container(keyedBy: CodingKeys.self)"
let encodes = properties.map { property in
let name = property.pattern.trimmedDescription
return "try container.encode(\(name), forKey: .\(name))"
}
return try FunctionDeclSyntax("""
func encode(to encoder: Encoder) throws {
\(raw: container)
\(raw: encodes.joined(separator: "\n"))
}
""")
}
}
// 扩展:判断是否为存储属性
extension VariableDeclSyntax {
var isStoredProperty: Bool {
// 排除计算属性(Computed Property)
bindings.allSatisfy { binding in
binding.accessorBlock == nil
}
}
}
二、开发工具与流程
- 1、SwiftSyntax:SwiftSyntax 是 Swift 官方提供的语法树(AST)解析与生成库,用于在代码层面分析、修改和生成 Swift 源码。它是实现宏(Macros)、代码格式化工具(如 SwiftFormat)以及静态分析工具(如 SwiftLint)的核心基础库。
- 2、Xcode 宏模板:通过 File → New → Package 选择 Swift Macro 模板快速创建项目。
- 3、 调试技巧:
展开宏:在 Xcode 中右键点击宏 → Expand Macro,查看生成的代码。
单元测试:通过 swift test 验证宏生成代码的正确性。
错误处理:
若宏实现抛出错误(如不支持的计算属性),编译器会在调用处显示错误信息。