我有一个类似于以下的课程:
final class Person {
@Resettable var name = "John Doe"
func revertName() {
name = $name
}
}
@propertyWrapper
struct Resettable<T> {
var wrappedValue: T
let projectedValue: T
init(wrappedValue: T) {
self.wrappedValue = wrappedValue
self.projectedValue = wrappedValue
}
}
但是,当我用新的
Person
宏注释 @Observable
时,出现以下错误:
属性包装器不能应用于计算属性
我假设发生这种情况是因为
@Observable
宏为类中的所有属性添加了 getter 和 setter。
有什么方法可以同时使用我的
@Resettable
属性包装器和 @Observable
宏吗?
上下文:就我而言,它是
class GameScene: SKScene
,我使用@Resettable
来重置诸如var runningSpeed
之类的东西。游戏场景必须用 @Observable
标记,因为 GameScene
是使用名为 GameSettingsView
的 SwiftUI 视图进行修改的。该视图使用 @Bindable
和 @Observable
来修改游戏场景。
你是对的 -
@Observable
将存储的属性转换为计算属性,因此你不能在它们上放置属性包装器。
您可以尝试将属性包装器也变成宏。对于你的
Resettable
来说,这相当简单。您只需生成一个新属性,以 $
为前缀,并具有相同的初始化程序。
我写的一个简单的实现(我不太擅长使用SwiftSyntax,所以请原谅)
public enum Resettable: PeerMacro {
public static func expansion(of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [DeclSyntax] {
// only apply to vars
guard let varDecl = declaration.as(VariableDeclSyntax.self),
varDecl.bindingSpecifier.text == "var" else {
return []
}
return varDecl.bindings.filter {
// apply only to those with initialisers
$0.initializer != nil &&
// and also the name of the variable does not start with underscore
!$0.pattern.as(IdentifierPatternSyntax.self)!.identifier
.text.starts(with: "_")
}.map {
return "let $\($0.pattern) = \($0.initializer!.value)" as DeclSyntax
}
}
}
@main
struct ResettablePlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [Resettable.self]
}
// ...
@attached(peer, names: prefixed(`$`))
public macro Resettable() = #externalMacro(module: "SomeModule", type: "Resettable")
备注:
@Observation
显然会生成一个@Resettable var _name
,而我们不想添加$_name
。