Search code examples
swiftdata-structurescomputed-properties

Setting Part of Swift Computed Structure


I was wondering if this behavior in the swift language is documented anywhere. I haven't been able to find it in the official documentation. It is best expressed in code:

var testBacking = CGPoint(x: 3, y: 5)
var testPoint:CGPoint {
    get {
        print("getter called")
        return testBacking
    } set {
        print("setter called with newValue = \(newValue)")
        testBacking = newValue
    }
}
testPoint.x = 10 //  getter called
                 //  setter called with newValue = (10.0, 5.0)

As you can see, I am only setting the x component of the computed structure testPoint, and in doing so, swift automatically calls the getter first and pulls out the y component and builds a complete structure that it then passes to the setter as newValue. This seems like appropriate behavior. My question is: Where is this behavior documented? Have I missed it, or is it simply not mentioned?


Solution

  • The way to understand this is to consider a similar but simpler case without your extra complications. Let's just talk about this:

    struct S {
        var name = "matt"
    }
    var s = S()
    s.name = "mogelbuster"
    

    How does that work? What happens when we set s.name? First, pull out the current value of s; then, we set its name property; then, we set the value of s to this new struct. (You can easily confirm that last part by putting a setter observer on s.)

    Setting a struct's property by way of a reference, then involves getting the reference's value (the struct), setting the property, and setting the reference's value with the new struct. That's because a struct is a value type. It cannot be mutated in place, so setting a property by way of a reference to a struct involves setting into the reference.

    What you are doing with testPoint is merely a computed-variable version of the same process. The mere act of speaking of testPoint.x means that we must get testBacking, to find out what it is. Thus, the getter is called. Then you set into testPoint.x, thus calling the setter to write the new value back into testBacking.


    Note that the same thing would not be true if we were working with a class. Here's a variation on your original example:

        class PointHolder {
            var point = CGPoint(x:3, y:5)
            var x : CGFloat {
                get { return point.x }
                set { point.x = newValue }
            }
        }
        var testBacking = PointHolder()
        var testPoint:PointHolder {
            get {
                print("getter called")
                return testBacking
            } set {
                print("setter called with newValue = \(newValue)")
                testBacking = newValue
            }
        }
        testPoint.x = 10 // getter called, but _not_ setter
    

    In the last line, the testPoint setter is not called — because a class instance is a reference type and is mutable in place. testBacking is changed without setting, because what we got from testBacking when we said testPoint.x, and the getter was called, is a reference; a change to this changes the thing testBacking points to without setting into it.