一、宏(Macros)
通過(guò)宏在編譯時(shí)生成代碼
宏(Macros)允許你在編譯期間轉(zhuǎn)換源代碼,從而避免手動(dòng)編寫重復(fù)代碼。在編譯過(guò)程中,Swift 會(huì)在常規(guī)代碼構(gòu)建前展開(kāi)所有宏,生成實(shí)際代碼。

宏展開(kāi)始終是一種增補(bǔ)操作:宏會(huì)添加新代碼,但絕不會(huì)刪除或修改現(xiàn)有代碼。
宏的輸入(原始代碼)和宏展開(kāi)后的輸出(生成代碼)均會(huì)經(jīng)過(guò)檢查,以確保它們是語(yǔ)法有效的 Swift 代碼。同樣,傳遞給宏的值以及宏生成代碼中的值也會(huì)被檢查,以確保類型正確。此外,如果宏的實(shí)現(xiàn)(在展開(kāi)過(guò)程中)遇到錯(cuò)誤,編譯器會(huì)將其視為編譯錯(cuò)誤。這些保障機(jī)制使得:使用宏的代碼更易于理解和推理;更容易識(shí)別問(wèn)題(例如錯(cuò)誤使用宏,或宏實(shí)現(xiàn)本身存在缺陷)。
二、宏的類型
Swift 有兩種類型的宏:
- 獨(dú)立宏(Freestanding Macros):獨(dú)立存在,不附加在任何聲明上。
- 附著宏(Attached Macros):修改它們所附加的聲明。
2.1、Freestanding Macros
要調(diào)用獨(dú)立宏,你需要在宏名稱前加上井號(hào)(#),并在名稱后的括號(hào)內(nèi)傳入?yún)?shù)。例如:
let c = #stringify(20)
那么如何定義一個(gè)宏呢?
@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):表明這是一個(gè)獨(dú)立宏,屬于表達(dá)式類別。 -
public macro stringify<T>(_ value: T) -> String、public macro Intstringify<T>(_ value: T) -> String、 public macro PublicStruct(_ name: String)這里就是宏的語(yǔ)法表達(dá)結(jié)構(gòu) -
#externalMacro(module: "MyMacroMacros", type: "StringifyMacro"),module:指定宏實(shí)現(xiàn)所屬模塊,需與 Package.swift 中的模塊名一致;type:實(shí)現(xiàn)該宏的具體類型,需繼承自 ExpressionMacro 協(xié)議。
在大多數(shù) Swift 代碼中,當(dāng)你實(shí)現(xiàn)一個(gè)符號(hào)(例如函數(shù)或類型)時(shí),并不需要單獨(dú)的聲明。然而對(duì)于宏而言,其聲明和實(shí)現(xiàn)是分離的。
下面看一下宏的實(shí)現(xiàn):
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)"
}
}
每個(gè)宏角色需遵循特定協(xié)議以實(shí)現(xiàn)代碼生成邏輯,例如:
ExpressionMacro → @freestanding(expression)
DeclarationMacro → @freestanding(declaration)
PeerMacro → @attached(peer)
AccessorMacro → @attached(accessor)
MemberMacro → @attached(member)
具體實(shí)現(xiàn)時(shí),需通過(guò) swift-syntax 庫(kù)解析語(yǔ)法樹(shù)(AST),并在 expansion 方法中生成代碼。
補(bǔ)充
獨(dú)立宏以 # 開(kāi)頭,無(wú)需依賴其他聲明,直接生成代碼。其角色包括:
- 表達(dá)式宏 @freestanding(expression)
生成一個(gè)表達(dá)式,并返回計(jì)算結(jié)果。例如 #stringify(a + b) 會(huì)被展開(kāi)為 (a + b, "a + b")13。 - 聲明宏 @freestanding(declaration)
生成新的聲明(如函數(shù)、結(jié)構(gòu)體等)。例如 #myStruct 可能生成一個(gè)包含特定屬性和方法的 struct15。
2.2、Attached Macros
要調(diào)用一個(gè)附加宏(Attached Macro),你需在其名稱前添加 @ 符號(hào),并將宏>的參數(shù)寫在名稱后的圓括號(hào)內(nèi)。
附加宏的作用是修改它們所附加的聲明,通過(guò)向該聲明添加代碼來(lái)實(shí)現(xiàn)功能擴(kuò)展,例如:
定義新方法
添加對(duì)協(xié)議的遵循(Conformance)
調(diào)用附加宏的方式與作用
示例說(shuō)明
假設(shè)有以下未使用宏的代碼:
struct User {
var name: String
var age: Int
}
通過(guò)附加宏(如 @Codable),可為該結(jié)構(gòu)體自動(dòng)生成 Codable 協(xié)議的編解碼實(shí)現(xiàn)代碼,無(wú)需手動(dòng)編寫。
@Codable // 附加宏自動(dòng)生成 Codable 協(xié)議實(shí)現(xiàn)
struct User {
var name: String
var age: Int
}
宏的定義和實(shí)現(xiàn)
import SwiftSyntaxMacros
/// 定義 `@Codable` 附加宏,自動(dòng)生成 `Codable` 協(xié)議實(shí)現(xiàn)
@attached(
member, // 角色:添加成員
names: named(CodingKeys), // 生成的符號(hào)名稱
named(init(from:)),
named(encode(to:))
)
public macro Codable() = #externalMacro(
module: "MyMacros", // 宏實(shí)現(xiàn)的模塊名
type: "CodableMacro" // 宏實(shí)現(xiàn)的具體類型
)
import SwiftSyntax
import SwiftSyntaxMacros
import SwiftSyntaxBuilder
public struct CodableMacro: MemberMacro {
/// 核心方法:解析聲明并生成代碼
public static func expansion(
of node: AttributeSyntax, // 宏屬性(如 `@Codable`)
providingMembersOf declaration: some DeclGroupSyntax, // 附加的聲明(如結(jié)構(gòu)體)
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// 1. 驗(yàn)證附加聲明是否為結(jié)構(gòu)體或類
guard let structDecl = declaration.as(StructDeclSyntax.self) ?? declaration.as(ClassDeclSyntax.self) else {
throw MacroError.message("`@Codable` 只能用于結(jié)構(gòu)體或類")
}
// 2. 提取所有存儲(chǔ)屬性(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"))
}
""")
}
}
// 擴(kuò)展:判斷是否為存儲(chǔ)屬性
extension VariableDeclSyntax {
var isStoredProperty: Bool {
// 排除計(jì)算屬性(Computed Property)
bindings.allSatisfy { binding in
binding.accessorBlock == nil
}
}
}
二、開(kāi)發(fā)工具與流程
- 1、SwiftSyntax:SwiftSyntax 是 Swift 官方提供的語(yǔ)法樹(shù)(AST)解析與生成庫(kù),用于在代碼層面分析、修改和生成 Swift 源碼。它是實(shí)現(xiàn)宏(Macros)、代碼格式化工具(如 SwiftFormat)以及靜態(tài)分析工具(如 SwiftLint)的核心基礎(chǔ)庫(kù)。
- 2、Xcode 宏模板:通過(guò) File → New → Package 選擇 Swift Macro 模板快速創(chuàng)建項(xiàng)目。
- 3、 調(diào)試技巧:
展開(kāi)宏:在 Xcode 中右鍵點(diǎn)擊宏 → Expand Macro,查看生成的代碼。
單元測(cè)試:通過(guò) swift test 驗(yàn)證宏生成代碼的正確性。
錯(cuò)誤處理:
若宏實(shí)現(xiàn)拋出錯(cuò)誤(如不支持的計(jì)算屬性),編譯器會(huì)在調(diào)用處顯示錯(cuò)誤信息。