I have a class which is similar to:
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
}
}
However, when I annotate Person
with the new @Observable
macro I got the following error:
Property wrapper cannot be applied to a computed property
I'm assuming this happens because the @Observable
macro adds a getter and setter to all properties in a class.
Is there any way I can use my @Resettable
property wrapper and the @Observable
macro at the same time?
Context: In my case it's class GameScene: SKScene
and I use @Resettable
to reset things like var runningSpeed
and whatnot. The game scene must be marked with @Observable
because the GameScene
is modified using a SwiftUI view called GameSettingsView
. This view uses @Bindable
and @Observable
to modify the game scene.
You are correct - @Observable
turns the stored properties into computed properties, so you cannot put property wrappers on them.
You can try turning your property wrapper also into a macro. For your Resettable
, this is rather simple. You just generate a new property, prefixed with $
with the same initialiser.
A simple implementation I wrote (I'm rather bad at using SwiftSyntax, so please excuse me)
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")
Notes:
@Observation
apparently generates a @Resettable var _name
, and we don't want to add a $_name
.