Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is didSet called again when setting inside through a function?

Tags:

swift

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
    }
}
like image 780
Afshin Avatar asked Jan 03 '19 19:01

Afshin


People also ask

Is didSet called on initialization?

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.

What does didSet mean in Swift?

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.

What is didSet?

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!

What are property observers in Swift?

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.


1 Answers

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 own didSet 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
        }
    }
}
like image 60
Afshin Avatar answered Sep 27 '22 20:09

Afshin