As far as I know and as mentioned in this thread, if I set a property value in its didSet
observer, it should not trigger the observer again. OK, then I wrote a piece of code like this:
class B {
var i = 0 {
didSet {
print("didSet called")
self.i += 1
}
}
}
var y = B()
y.i = 2
print(y.i)
This code prints "didSet called"
and 3
as output as expected. But I made a small change to this code as follows:
class B {
var i = 0 {
didSet {
print("didSet called")
doit(val: self)
}
}
func doit(val: B) {
val.i += 1
}
}
var y = B()
y.i = 2
print(y.i)
But now it falls into an infinite loop printing "didSet called"
. Why if I set a value to the variable inside didSet
via passing it through a function argument, does it trigger didSet
again? Since the passed object should refer to the same object, I don't know why this happens. I tested and if I set it via a closure in didSet
rather than a normal function it goes to an infinite loop again.
Update: It is funny that even this triggers an infinite loop:
class B {
var i = 0 {
didSet {
print("called")
doit()
}
}
func doit() {
self.i += 1
}
}
willSet and didSet observers are not called when a property is first initialized. They are only called when the property's value is set outside of an initialization context.
Property Observers in Swift. In Swift, you can attach property observers to variables to run code when the variable changes. These property observers are called willSet and didSet. The former runs right before the property changes, and the latter runs right after the changes were made.
didSet is called right after the data is stored and it has a default constant oldValue which shows the previous value that is overwritten. willSet and didSet cannot be used at computed property since set function has already covered their functionalities and getter only stored property cannot be set actually!
Property Observers. Property observers observe and respond to changes in a property's value. Property observers are called every time a property's value is set, even if the new value is the same as the property's current value.
After checking with Swift github and asking questions about this problem, I find out that this problem is more complex as it seems. But there is a specific rule about this problem:
didSet
observer will not trigger only if access to property within its owndidSet
observer can be done through direct memory access.
Problem is that it is a little ambiguous when access to property will be direct(unless probably if you are developer of Swift). An important feature that has an effect on my question is this:
Class instance method never access class properties directly.
This quote shows problem with my code, even though I can argue that when an instance member should be able to access property directly whenever you call it in didSet
observe. When I have a code like this:
class B {
var i = 0 {
didSet {
print("called")
doit()
}
}
func doit() {
self.i += 1
}
}
doit()
function cannot access i
directly which triggers didSet
again causing infinite loop.
Now what is the workaround?
You can use inout
for passing properties from its own didSet
to a instance function without triggering didSet
. Something like this:
class B {
var i = 0 {
didSet {
print("called")
doit(&i)
}
}
func doit(_ i: inout Int) {
i += 1
}
}
And one last thing. Starting Swift 5, conditions for selecting direct memory access for properties within its own didSet
will become more restricted. Based on github, only conditions that will use direct memory access is are the following:
Within a variable's own didSet/willSet specifier, access its storage directly if either: 1) It's a 'plain variable' (i.e a variable that's not a member). 2) It's an access to the member on the implicit 'self' declaration. If it's a member access on some other base, we want to call the setter as we might be accessing the member on a *different* instance.
This means codes like following will trigger infinite loop while it does not right now:
class B {
var i = 0 {
didSet {
print("called")
var s = self
s.i += 1
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With