rayx
(Huan Xiong)
1
I thought it didn't work, but my experiment shows it works fine.
@Observable
final class Foo {
var x: Int = 0 {
didSet {
print("didSet was called")
}
}
}
func test() {
var foo = Foo()
withObservationTracking {
let _ = foo.x
} onChange: {
print("x was changed")
}
foo.x = 1
}
test()
// Output:
// x was changed
// didSet was called
So I expand macro in Xcode and find the following
@ObservationTracked var x: Int = 0 {
didSet {
print("didSet was called")
}
}
is expanded to:
var x: Int = 0 {@storageRestrictions(initializes: _x)
init(initialValue) {
_x = initialValue
}
get {
access(keyPath: \.x)
return _x
}
set {
withMutation(keyPath: \.x) {
_x = newValue
}
}
didSet {
print("didSet was called")
}
}
I knew didSet couldn't be used with computed variable before, so I thought this was a new feature. However, when I try to reproduce the behavior with my own code, it can't compile.
struct Bar {
var _x: Int = 0
var x: Int {
@storageRestrictions(initializes: _x)
init(initialValue) {
_x = initialValue
}
get {
return _x
}
set {
_x = newValue
}
// Compiler error: 'didSet' cannot be provided together with an init accessor
didSet {
print("didSet was called")
}
}
}
Any idea why it works in macro expanded code but not in my own code? Thanks.
rayx
(Huan Xiong)
2
To answer my own question, the willSet/didSet support is by design, because there is a section about it in SE-0395. The doc also described how to implement it:
var a: Int {
get {
self.access(keyPath: \.a)
return _a
}
set {
self.withMutation(keyPath: \.a) {
_a = newValue
}
}
}
var _a = 0 {
willSet { print("will set triggered") }
didSet { print("did set triggered") }
}
However, it seems the actual approach is different. From what I can tell from ObservableMacro code, it doesn't deal with willSet/didSet accessors at all. My hypothesis is that the compiler has internal support for computer property's willSet/didSet accessors (IIUIC property wrapper needs it). But it doesn't allow user to input it explicitly. That's why my manual code can't compile. If the hypothesis is correct, I should be able to reproduce this behavior (transforming a stored property to computed property and expect its willSet/didSet accessors still work) by writing my own macro. I'll give it a try after resolving the macro development issue on my MBP.
1 Like
rayx
(Huan Xiong)
3
My mistake. I didn't realize it's implicitly supported by this line. The test below also verifies the didSet accessor is attached to the storage, instead of the computed variable.
@Observable
final class Foo {
var x: Int = 0 {
didSet {
print("didSet was triggered")
}
}
func modifyStorage(_ value: Int) {
_x = value
}
}
func test() {
let foo = Foo()
withObservationTracking {
let _ = foo.x
} onChange: {
print("x was changed")
}
foo.modifyStorage(3)
}
test()
// Output:
// didSet was triggered