0

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?

3
  • 2
    Please post code as text. Don't post images of code. Commented Jun 22, 2021 at 7:05
  • you're looking for a lazy get stackoverflow.com/a/44164693/294884 Commented Mar 19 at 17:06
  • This question is similar to: lazy var: Cannot use mutating getter on immutable value. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem. Commented Mar 19 at 17:06

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

Comments

0

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.

1 Comment

I asked the question here forums.swift.org/t/…. The answer is the same as the accepted answer on this question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.