It looks like the compiler attaches the willSet
observer to the computed property n
, not the stored property _n
. So your code:
struct Bar {
@Foo var n = 1 {
willSet {
print(newValue)
}
}
}
gets translated into this pseudocode (pseudcode because computed properties can't have observers, so the willSet
code would be included in the setter):
struct Bar {
private var _n: Foo = Foo(wrappedValue: 1)
var n: Int {
get { _n.wrappedValue }
willSet {
print(newValue)
}
set {
_n.wrappedValue = newValue
}
}
}
The projected value doesn't go through this computed property, that's why it doesn't trigger the willSet
. You're right that all paths access the internal storage in the end, but the willSet
isn't attached to that storage.
Yes, b and c.
I suppose another option would have been to attach the observers on the stored property private var _n: Foo
instead of the computed property var n: Int
. That would match your expectations because mutating Foo.wrappedValue
(through any code path) is also a mutation of Foo
(if Foo
is a value type). But then the willSet
observer would fire for any mutation of Foo
, not just when wrappedValue
is mutated. This would probably be equally or even more surprising than the existing behavior. Regardless, I doubt the behavior can be changed now because it could break existing code.