skip to Main Content

I have a lazy property in a struct and every time I access it, it mutates the struct.

var numbers = [1,2,3]

struct MyStruct {
    
    lazy var items = numbers
}

class MyClass {
    var myStructPropery: MyStruct = MyStruct() {
        didSet {
            print(myStructPropery)
        }
    }
}

var myClass = MyClass()
myClass.myStructPropery.items
myClass.myStructPropery.items
myClass.myStructPropery.items

Result:

Print (didSet) will be called every time.

The ideal behaviour should be first time mutation only (since that’s how lazy variables behave). Is this a bug in Swift or am I doing something wrong?

2

Answers


  1. I started debugging this with following set up –

    var numbers = [1,2,3]
    
    struct MyStruct {
        lazy var items = numbers
    }
    
    class MyClass {
        var myStructPropery: MyStruct = MyStruct() {
            didSet {
                // Changed this to make sure we are not invoking getter here
                print("myStructPropery setter called")
            }
        }
    }
    
    let myClass = MyClass()
    myClass.myStructPropery.items
    myClass.myStructPropery.items
    myClass.myStructPropery.items
    

    I can reproduce the problem on Xcode 12.5 using Swift 5.4.

    Attempt 1 : Turn var numbers into let numbers – Does NOT work.

    let numbers = [1,2,3]
    

    Attempt 2 : Assign the value inline without using an extra variable – Does NOT work.

    struct MyStruct {
        lazy var items = [1,2,3]
    }
    

    Attempt 3 : Assign the value inline using the full blown getter syntax – Does NOT work.

    struct MyStruct {
        lazy var items: [Int] = {
            return [1,2,3]
        }()
    }
    

    At this point, we are out of options to try. Even though we can clearly see that return [1,2,3] in the last attempt is executed exactly once, the MyClass.myStructPropery.modify is called repeatedly on access to items.

    Maybe Swift Forums is a better place to discuss this.

    Login or Signup to reply.
  2. What you are seeing is is that the observed property items has notified its observer that it has been mutated because it was accessed, and a lazy variable is by definition mutating since it will be set at a later time. Therefore didSet gets called to handle this.

    The lazy variable is actually only set once even though it signals that it has mutated and the property myStructPropery is only mutated once when the variable is first set but is the same instance after that.

    Here is how we can verify this, first change the lazy var declaration so it’s more like how we usually declare such a variable

    lazy var items: [Int] = { numbers }() 
    

    and then add a print statement

    lazy var items: [Int] = {
        print("inside lazy")
        return numbers
    }()
    

    If we now run the test code

    var myClass = MyClass()
    myClass.myStructProperty.items
    myClass.myStructProperty.items
    myClass.myStructProperty.items 
    

    we see that "inside lazy" only prints once. To verify that the property myStructProperty isn’t changed we can make the struct conform to Equatable and perform a simple check inside didSet

    didSet {
        if oldValue != myStructProperty {
            print(myStructProperty)
        }
    }
    

    Now running the test we see that the print inside didSet is never executed so myStructProperty is never changed.

    I have no idea if this behaviour is a bug but personally it feels like it might be complicate for the property observer to stop observing a lazy property once it was accessed or for a lazy var to not be defined as mutating once it is set.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search