You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中自定义Peer Macro时如何兼容@Observable宏展开?

解决@Resettable宏与@Observable宏的兼容问题

问题背景

实现了一个Resettable Peer Macro,用于为附加的变量声明生成同初始值的私有常量,代码如下:

public struct ResettableMacro: PeerMacro {
    public static func expansion(of node: AttributeSyntax,
                                 providingPeersOf declaration: some DeclSyntaxProtocol,
                                 in context: some MacroExpansionContext)
    throws -> [DeclSyntax] {
        guard let variableDecl = declaration.as(VariableDeclSyntax.self) else {
            print("Can only be applied to variable declarations") 
            return []
        }
        guard variableDecl.bindingSpecifier.text == "var" else {
            print("Can only be a variable")
            return []
        }
        guard variableDecl.bindings.allSatisfy({ $0.initializer != nil }) else {
            print("Must be an initialised stored property")
            return []
        }
        
        // abstracted extracting the identifier pattern and the initial value for clarity
        return zip(variableDecl.identifiers, variableDecl.values).map { name, value in
            "private let $\(raw: name) = \(raw: value)"
        }
    }
}

当将@Resettable应用于@Observable类的属性时,会触发错误:'self' used in property access '$_name' before 'self.init' call,示例代码:

@Observable
class Person { 
    @Resettable var name = "John Doe"
        // << error here due to @ObservationTracked
}

问题原因

@Observable宏展开时,会将原始属性转换为带有访问器的计算属性,并生成一个附加了@Resettable和@ObservationIgnored的私有存储属性:

@Resettable  @ObservationIgnored private var _name  = "John Doe"

这个宏生成的私有属性会被Resettable宏再次处理,生成的$_name常量会在self.init完成前被访问,从而触发错误。

最简解决方案

在Resettable宏的逻辑中新增判断,跳过带有private访问修饰符的变量声明——因为@Observable生成的内部属性必然是私有的,而用户手写的需要重置的属性通常不会是私有(若需支持私有属性,可额外结合下划线前缀判断,但跳过私有变量已覆盖绝大多数场景)。

修改后的宏代码:

public struct ResettableMacro: PeerMacro {
    public static func expansion(of node: AttributeSyntax,
                                 providingPeersOf declaration: some DeclSyntaxProtocol,
                                 in context: some MacroExpansionContext)
    throws -> [DeclSyntax] {
        guard let variableDecl = declaration.as(VariableDeclSyntax.self) else {
            print("Can only be applied to variable declarations") 
            return []
        }
        guard variableDecl.bindingSpecifier.text == "var" else {
            print("Can only be a variable")
            return []
        }
        // 新增:跳过私有变量,避免处理宏生成的内部属性
        guard !variableDecl.modifiers.contains(where: { $0.name.text == "private" }) else {
            return []
        }
        guard variableDecl.bindings.allSatisfy({ $0.initializer != nil }) else {
            print("Must be an initialised stored property")
            return []
        }
        
        // abstracted extracting the identifier pattern and the initial value for clarity
        return zip(variableDecl.identifiers, variableDecl.values).map { name, value in
            "private let $\(raw: name) = \(raw: value)"
        }
    }
}

内容的提问来源于stack exchange,提问作者rayaantaneja

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 01:20:17