SLN SLN - 2 months ago 5
Swift Question

Why constant constraints the property from a structure instance but not the class instance?

When I trying to change the

ID
property of the
byValueObj
instance, I received an error that told me I cannot assign to the property of a constant, even though the property is a variable. However, I can do it on a class instance. I kind of knowing that it maybe has something to do with the by value and by reference mechanism. But I don't have a very clear and correct understanding of it. Can someone explain it for me? Thanks.

struct CreatorValue{
var ID = 2201
}
class CreatorRefer{
var ID = 2203
}

let byValueObj = CreatorValue()
let byReferObj = CreatorRefer()

byValueObj.ID = 201 //Error: cannot assign to property: 'byValueObj' is a 'let' constant
byReferObj.ID = 203 //works fine here

Answer

Value types in Swift are immutable. When you come to 'mutate' them, be it via changing the value of a property, or through using a mutating function – you're actually creating a copy of that value type with the mutation applied, and then assigning it back to the original variable – thus violating the let constant.

You can think of changing the property on value type like this:

struct CreatorValue {
    var id = 2201
}

// your original instance
let valueTypeInstance = CreatorValue()

// think of 'valueTypeInstance.id = 201' as doing the following...

var valueTypeCopy = valueTypeInstance // creates copy of your value type instance
valueTypeCopy.id = 201 // change the property

// reassign to original variable
valueTypeInstance = valueTypeCopy // error: Cannot assign to value: 'valueTypeInstance' is a 'let' constant

As you can clearly see here – this code is illegal. You cannot assign valueTypeCopy back to valueTypeInstance – as it's a let constant. Hence, you cannot change the property of a value type that is declared as a let, and would therefore need make it a var.

You can even prove that a copy gets created upon changing a property (as @MartinR demonstrated the other day), through keeping a reference to the original instance via an instance method (as instance methods are reference types).

struct CreatorValue {
    var id = 2201

    func printID() {
        print(id)
    }
}

// your original instance
var valueTypeInstance = CreatorValue()

// keep reference to original valueTypeInstance via its instance method
let printID = valueTypeInstance.printID 

// change property – thus creating a copy, which is then re-assigned to valueTypeInstance
valueTypeInstance.id = 201

// get the instance method of the copy
let printIDOfCopy = valueTypeInstance.printID

// call both methods
printID() // the original instance (prints: 2201)
printIDOfCopy() // the copy of the instance that got created when changing the property (prints: 201)

The reason you can change a var property of a let constant class instance, is due to the fact that classes are both mutable and reference types. Therefore being a let constant only ensures that the reference stays the same. You can mutate their properties directly, which doesn't in any way affect your reference to them – you're still referring to the same instance.

You can think of a reference type like a signpost, therefore code like this:

class CreatorRefer{
    var id = 2203
}

let referenceTypeInstance = CreatorRefer()

you can think of like this:

referenceTypeInstance ---------> [ CreatorRefer Instance ]
                                 [        id : 2203      ]

And when you mutate a variable:

referenceTypeInstance.id = 203

The reference itself isn't affected – you're still pointing to the same location in memory. It's the property of the underlying instance that's changed (meaning the underlying instance was mutated):

referenceTypeInstance ---------> [ CreatorRefer Instance ]
                                 [        id : 203       ]
Comments