Why Are Properties of an Immutable Object Mutable in Swift

Why are properties of an immutable object mutable in Swift?

According to the Swift Programming Language, the properties of constant structs are also constant, but constant classes can have mutable properties.

In their words,

If you create an instance of a structure and assign that instance to a constant, you cannot modify the instance’s properties, even if they were declared as variable properties...

The same is not true for classes, which are reference types. If you assign an instance of a reference type to a constant, you can still change that instance’s variable properties.

What are the benefits of an immutable struct over a mutable one?

Different approaches will facilitate different kinds of changes to the code. An immutable structure is very similar to an immutable class object, but a mutable structure and a mutable class object are very different. Thus, code which uses an immutable structure can often be readily adapted if for some reason it becomes necessary to use a class object instead.

On the flip side, use of an immutable object will often make the code to replace a variable with a modified version more brittle in case additional properties are added to the type in question. For example, if a PhoneNumber type includes methods for AreaCode, LocalExchange, and LocalNumber and a constructor that takes those parameters, and then adds an "optional" fourth property for Extension, then code which is supposed to change the area codes of certain phone numbers by passing the new area code, LocalExchange, and LocalNumber, to the three-argument constructor will erase the Extension property of every phone number, while code which could write to AreaCode directly wouldn't have had that problem.

Swift let is mutable in classes why?

The "Modifying Constant Properties During Initialization" heading under the Initialization section of The Swift Programming Language says:

You can modify the value of a constant property at any point during
initialization, as long as it is set to a definite value by the time
initialization finishes.

Reading between the lines, and considering your example, it sounds very much like restrictions on setting the value of a constant don't apply to initialization. Further evidence supporting that idea appears earlier in the same section:

When you assign a default value to a stored property, or set its
initial value within an initializer, the value of that property is set
directly, without calling any property observers.

It's not unlikely that the constancy of a stored property is enforced by the accessors for that property. If those accessors aren't used during initialization, then it makes sense that you can modify even a constant property as many times as you like during initialization.

The fact that you can't modify j in your example after first setting it is due to the fact that j is a local constant, not a property. There probably aren't any accessors for j at all -- instead the compiler probably enforces access rules for local constants/variables.

Explain the difference between mutable and immutable

A mutable object can be mutated or changed. An immutable object cannot. For example, while you can add or remove objects from an NSMutableArray, you cannot do either with an NSArray.

Mutable objects can have elements changed, added to, or removed, which cannot be achieved with immutable objects. Immutable objects are stuck with whatever input you gave them in their [[object alloc] initWith...] initializer.

The advantages of your mutable objects is obvious, but they should only be used when necessary (which is a lot less often than you think) as they take up more memory than immutable objects.

How to build a Swift object that can control the mutability of its stored properties

import Foundation

protocol PropertyWrapperWithLockableObject {
var enclosingObject: LockableObjectBase! {get set}
}

@propertyWrapper
class Lockable<Value>: PropertyWrapperWithLockableObject {
private var _wrappedValue: Value
var enclosingObject: LockableObjectBase!

init (wrappedValue: Value) { self._wrappedValue = wrappedValue }

var wrappedValue: Value {
get {
precondition(enclosingObject.isLocked, "Cannot access object properties until object is locked")
return _wrappedValue
}
set {
precondition(!enclosingObject.isLocked, "Cannot modify object properties after object is locked")
_wrappedValue = newValue
}
}
}

class LockableObjectBase {
internal var isLocked: Bool = false {
didSet { isLocked = true }
}

init () {
let mirror = Mirror(reflecting: self)
for child in mirror.children {
if var child = child.value as? PropertyWrapperWithLockableObject {
child.enclosingObject = self
}
}
}
}

Usage:

class DataObject: LockableObjectBase {
@Lockable var someString: String = "Zork"
@Lockable var someInt: Int

override init() {
someInt = 42
// super.init() // Not needed in this particular example.
}
}

var testObject = DataObject()
testObject.isLocked = true
print(testObject.someInt, testObject.someString) // 42, Zork
testObject.isLocked = false // Has no effect: Object remained locked
print (testObject.isLocked) // true
testObject.someInt = 2 // Aborts the program

arsenius's answer here provided the vital reflection clue!

How to store immutable arrays in a variable stored property in Swift?

The question is oddly posed, since your first myItems is not an array of various arrays, it's just an array of Strings. However, I'll try to discuss the matter in a more general way.

Swift does not distinguish mutable from immutable arrays in the way Cocoa NSArray does. But why do you need this? Cocoa needs it because an NSArray is a class, so if it were mutable, it could be changed behind MyClass's back. But in Swift, Array is a value type, so that can't happen.

So what's the problem you are really trying to solve? You should ask yourself what contract you are trying to get outside objects to fulfill, and try to write that contract into the structure of MyClass. Your array of structs is a perfectly reasonable approach. I think, though, that what you're after here might actually be privacy, not immutability. For example, I might say this:

class MyClass{
private var myItemsHidden = [String]()
var myItems:[String] {
get {
return myItemsHidden
}
set {
myItemsHidden = newValue
}
}
}

Now, assuming MyClass is alone in its own file (because private in Swift means private to a file), myItemsHidden can be manipulated in any way you like from inside MyClass, so presumably MyClass knows not to mutate any subarrays or whatever it is you are trying to prevent. It is up to MyClass what it allows itself to do with myItemsHidden.

But from outside MyClass, myItemsHidden does not exist; there is only myItems, and the only thing anyone can do to it is get it and set it. (If they get it and change it, the original is unaffected. They cannot mutate the original.) If your purpose is to prevent anyone from outside setting myItems at all, delete the setter function:

class MyClass{
private var myItemsHidden = [String]()
var myItems:[String] {
get {
return myItemsHidden
}
}
}

Now myItems is merely a dispensed (vended) array and no more.

If the idea is that other classes should be allowed to add new arrays to an array of arrays, you could add a MyClass method that lets them do that. In other words, now that this thing is private, it is up to you what kind of access you give others to it. MyClass becomes the gatekeeper for what other classes can do with this value, because the value itself is hidden behind the private wall.

Why are iterators in forEach immutable for a struct but mutable for a class?

It is not true, that structures are immutable. But you change a different instance of the structure.

Swift obfuscates the way objects and structures are treated.

Objects are reference types, structures are value types. That means, that iterating over objects passes a reference to the object as argument and changing the object that the reference points to, is changing the the original object.

Structures are value types. A new instance of the structure is passed as argument. Moreover it is constant in this case. But even if you could change this, this would not effect the original instance of the structure.

In other programming language this different level of indirection is visible, i. e. in Objective-C by an *. In Swift it isn't.

Is there any way to make the method return a mutable value?

The callAsFunction simply returns (a copy of the) Person, which is a value type. You cannot then mutate the property of it like that. It is equivalent to the following:

struct Person {
var name: String
}

Person(name: "Foo").name = "Bar"

That returns the same error:

Sample Image

If Person was a reference type, it would have worked, but not for a value type. And even if you took your value type, and first assigned it to a variable before mutating it, you would only be mutating your copy, not the original.

If you want the behavior you want, you would use a @dynamicMemberLookup as suggested by matt (+1) and outlined in SE-0195.


You said:

I can't use dynamicMemberLookup because I don't know what method or property there will be in Person. For example, there may be 100 methods and properties in Person (not only one name property as demonstrated), and it is impossible for me to write 100 subscript methods with dynamicMemberLookup.

You do not need “100 subscript methods.” It is the motivating idea behind @dynamicMemberLookup, namely that the properties will be determined dynamically. E.g., here is Person with two properties, but Group only has the one @dynamicMemberLookup.

struct Person {
var name: String
var city: String
}

@dynamicMemberLookup
struct Group {
var person: Person
subscript(dynamicMember keyPath: WritableKeyPath<Person, String>) -> String {
get { person[keyPath: keyPath] }
set { person[keyPath: keyPath] = newValue }
}
}

var group = Group(person: Person(name: "James", city: "New York"))
group.name = "Wong"
group.city = "Los Angeles"
print(group.person) // Person(name: "Wong", city: "Los Angeles")

If you want to handle different types, make it generic:

struct Person {
var name: String
var city: String
var age: Int
}

@dynamicMemberLookup
struct Group {
var person: Person
subscript<T>(dynamicMember keyPath: WritableKeyPath<Person, T>) -> T {
get { person[keyPath: keyPath] }
set { person[keyPath: keyPath] = newValue }
}
}

And

var group = Group(person: Person(name: "James", city: "New York", age: 41))
group.name = "Wong"
group.city = "Los Angeles"
group.age = 42
print(group.person) // Person(name: "Wong", city: "Los Angeles", age: 42)

readonly mutable fields in Swift

By default, protocol typed objects have value value semantics. As a consequence, they're not mutable if the variable is a let constant.

To introduce reference semantics (and by extension, the mutability of objects referred to be a let constant), you need to make your protocol into a class protocol:

protocol Bar: class {
var fleem: Int? { get set }
}


Related Topics



Leave a reply



Submit