'Swift 4.2) Mutate array of struct with for_in/forEach vs. access by index
I am trying to modify struct element in array. I found that you can do that by accessing(iterate) the struct by index, but you can't if you use 'for in' loop or forEach{}.
struct Person
{
var age = 0
var name = "James"
}
var personArray = [Person]()
personArray += [Person(), Person(), Person()]
personArray.forEach({$0.age = 10}) // error: "Cannot assign to property: '$0' is immutable"
for person in personArray {
person.age = 10 // error: "Cannot assign to property: 'person' is a 'let' constant"
}
for index in personArray.indices {
personArray[index].age = 10 // Ok
}
Can someone explain?
Solution 1:[1]
As stated in other answers you can't mutate in a for-in
loop or in a .forEach
method.
You can either use your last formulation which is short and concise:
for index in personArray.indices {
personArray[index].age = 10
}
Or mutate the orignal personArray
entirely:
personArray = personArray.map { person in
var person = person // parameter masking to allow local mutation
person.age = 10
return person
}
Note that the second option may seems less efficient as it creates a new instance of Person
each time but Swift seems to be well optimised for those cases.
Here is a bonus if you really want a mutating counterpart for .forEach
method :
extension MutableCollection {
mutating func mutateEach(_ body: (inout Element) throws -> Void) rethrows {
for index in self.indices {
try body(&self[index])
}
}
}
This is a wrapper around array mutation equivalent to your first formulation, you can use it like intended:
personArray.mutateEach { $0.age = 10 }
Solution 2:[2]
In Swift a struct is a value type. In the for or foreach loop the person item is a value, and if it were mutable you would only be changing a copy of the original, not the original as you intend.
If you really want an updatable reference to the struct inside the loop add a var keyword, but remember you are updating a copy not the original.
for var person in personArray {
person.age = 10 // updates a copy, not the original
}
By contrast class is a reference type and in the loop each item is a reference to the original. Updating this reference value now updates the original.
Change your definition of Person to class instead of struct and it will work as expected. For a complete explanation see https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |