宏在编译时转换你的源代码,让你避免手动编写重复代码。在编译过程中,Swift 会先展开代码中的所有宏,再按常规流程构建代码。

宏扩展始终是增量操作:宏只添加新代码,从不删除或修改现有代码。
宏的输入和宏扩展的输出都会经过检查,确保其语法是合法的 Swift 代码。同样地,传递给宏的值和宏生成的代码中的值也会经过类型检查。此外,如果宏实现在展开时遇到错误,编译器会将其视为编译错误。这些保证使得使用宏的代码更易推理,也更容易发现诸如宏使用不当或实现存在缺陷等问题。
Swift 有两种宏:
- 独立宏(Freestanding macros)独立存在,不依附于任何声明。
- 附加宏(Attached macros)修饰它们所依附的声明。
两者的调用方式略有不同,但都遵循相同的扩展模型,且实现方式也相同。下文将详细阐述这两种宏。
独立宏
调用独立宏时,需在宏名前添加井号(#),参数写在宏名后的括号中。例如:
func myFunction() {    print("当前正在执行 \(#function)")    #warning("有问题")}第一行中的 #function 调用了 Swift 标准库中的 function() 宏。编译时,Swift 会调用该宏的实现,将 #function 替换为当前函数名。运行此代码并调用 myFunction() 时,会输出 “当前正在执行 myFunction()“。第二行中的 #warning 调用了 Swift 标准库的 warning(_:) 宏,用于生成自定义编译时警告。
独立宏可以像 #function 一样生成值,也可以像 #warning 一样在编译时执行操作。
附加宏
调用附加宏时,需在宏名前添加 @,参数写在宏名后的括号中。
附加宏会修改它们所依附的声明,例如定义新方法或添加协议一致性。
例如,考虑以下未使用宏的代码:
struct SundaeToppings: OptionSet {    let rawValue: Int    static let nuts = SundaeToppings(rawValue: 1 << 0)    static let cherry = SundaeToppings(rawValue: 1 << 1)    static let fudge = SundaeToppings(rawValue: 1 << 2)}这段代码中,每个选项都需手动调用初始化器,既重复又易错。使用宏后的版本:
@OptionSet<Int>struct SundaeToppings {    private enum Options: Int {        case nuts        case cherry        case fudge    }}@OptionSet 宏会读取私有枚举中的 case,为每个选项生成常量,并添加对 OptionSet 协议的遵循。展开后的代码:
struct SundaeToppings {    private enum Options: Int {        case nuts        case cherry        case fudge    }
    typealias RawValue = Int    var rawValue: RawValue    init() { self.rawValue = 0 }    init(rawValue: RawValue) { self.rawValue = rawValue }    static let nuts: Self = Self(rawValue: 1 << Options.nuts.rawValue)    static let cherry: Self = Self(rawValue: 1 << Options.cherry.rawValue)    static let fudge: Self = Self(rawValue: 1 << Options.fudge.rawValue)}extension SundaeToppings: OptionSet { }宏声明
宏的声明与实现分离。声明使用 macro 关键字引入:
@attached(member)@attached(extension, conformances: OptionSet)public macro OptionSet<RawType>() =        #externalMacro(module: "SwiftMacros", type: "OptionSetMacro")- @attached属性指定宏角色(如添加成员或扩展)
- #externalMacro指定实现位置
- 附加宏使用大驼峰命名,独立宏使用小驼峰命名
- 必须声明为 public
宏扩展流程
- 构建抽象语法树(AST):编译器将源代码转换为内存中的树形结构
- 发送部分 AST 到宏实现:仅包含与宏调用相关的节点
- 生成新 AST:宏实现返回扩展后的代码结构
- 替换和继续编译:编译器使用扩展后的代码继续编译流程
示例:#fourCharacterCode("ABCD") 的扩展过程:
let magicNumber = 1145258561 as UInt32实现宏
需创建两个组件:
- 执行扩展的宏类型
- 声明宏的库
使用 Swift Package Manager 创建模板:
swift package init --type macroPackage.swift 配置示例:
import PackageDescriptionimport CompilerPluginSupport
let package = Package(    name: "MyPackage",    platforms: [.macOS(.v10.15)],    dependencies: [        .package(url: "https://github.com/apple/swift-syntax", from: "509.0.0")    ],    targets: [        .macro(            name: "MyMacros",            dependencies: [                .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),                .product(name: "SwiftCompilerPlugin", package: "swift-syntax")            ]        ),        .target(name: "MyLib", dependencies: ["MyMacros"])    ])四字符编码宏实现示例:
public struct FourCharacterCode: ExpressionMacro {    public static func expansion(        of node: some FreestandingMacroExpansionSyntax,        in context: some MacroExpansionContext    ) throws -> ExprSyntax {        guard let literal = node.argumentList.first?.expression,              let string = literal.as(StringLiteralExprSyntax.self)?.segments.first else {            throw CustomError.message("需要静态字符串")        }
        guard let code = calculateCode(string) else {            throw CustomError.message("无效四字符编码")        }
        return "\(raw: code) as UInt32"    }}调试与测试
利用 SwiftSyntax 的测试能力:
let source: SourceFileSyntax = """let abcd = #fourCharacterCode("ABCD")"""
let context = BasicMacroExpansionContext()let transformed = source.expand(macros: ["fourCharacterCode": FourCharacterCode.self], in: context)
assert(transformed.description == "let abcd = 1145258561 as UInt32")通过预编译时的代码转换,Swift 宏系统在保持类型安全的同时,显著提升了代码的简洁性和可维护性。开发者可以借此消除大量模板代码,同时享受编译时的安全保障。
 
  