Swift学习之Macros

一、宏(Macros)

通过宏在编译时生成代码

宏(Macros)允许你在编译期间转换源代码,从而避免手动编写重复代码。在编译过程中,Swift 会在常规代码构建前展开所有宏,生成实际代码。


截屏2025-04-30 13.44.05.png

宏展开始终是一种增补操作:宏会添加新代码,但绝不会删除或修改现有代码。

宏的输入(原始代码)和宏展开后的输出(生成代码)均会经过检查,以确保它们是语法有效的 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 验证宏生成代码的正确性。
    错误处理:
    若宏实现抛出错误(如不支持的计算属性),编译器会在调用处显示错误信息。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 什么是宏 Apple 在 Swift 5.9 里面加入了 Swift macros(宏),宏可以在编译的过程中帮我...
    RayJiang97阅读 1,255评论 0 3
  • 什么是 Swift macro Macro 是 Swift 5.9 的新特性之一。 在 OC 中,宏其实就是代码替...
    zzzworm阅读 67评论 0 0
  • Swift 5.9 内置于 Xcode 15,虽然是 Swift 5 的最后一个大版本,仍然增加了不少新特性。 i...
    YungFan阅读 742评论 3 4
  • 一、Swift Macro介绍 WWDC2023会上Swift 5.9加入了Swift Macro,它允许我们在编...
    2525252472阅读 356评论 0 1
  • Swift 分享大纲 Swift 简介 Swift 优缺点[https://www.jianshu.com/p/2...
    阳光下的灰尘阅读 5,662评论 1 8