Swift學(xué)習(xí)之Macros

一、宏(Macros)

通過(guò)宏在編譯時(shí)生成代碼

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


截屏2025-04-30 13.44.05.png

宏展開(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ò)誤信息。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 什么是宏 Apple 在 Swift 5.9 里面加入了 Swift macros(宏),宏可以在編譯的過(guò)程中幫我...
    RayJiang97閱讀 1,367評(píng)論 0 3
  • 什么是 Swift macro Macro 是 Swift 5.9 的新特性之一。 在 OC 中,宏其實(shí)就是代碼替...
    zzzworm閱讀 215評(píng)論 0 0
  • Swift 5.9 內(nèi)置于 Xcode 15,雖然是 Swift 5 的最后一個(gè)大版本,仍然增加了不少新特性。 i...
    YungFan閱讀 797評(píng)論 3 4
  • 一、Swift Macro介紹 WWDC2023會(huì)上Swift 5.9加入了Swift Macro,它允許我們?cè)诰?..
    2525252472閱讀 532評(píng)論 0 1
  • Swift 分享大綱 Swift 簡(jiǎn)介 Swift 優(yōu)缺點(diǎn)[http://www.itdecent.cn/p/2...
    陽(yáng)光下的灰塵閱讀 5,912評(píng)論 1 8

友情鏈接更多精彩內(nèi)容