简体   繁体   中英

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?

As stated in other answers you can't mutate in a for-in loop or in a .forEach method.

You can either use you last formulation that 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. Time profiler reported NEAR 2x faster operation for the second option with an array of 1 000 000 Person s.

Here is a bonus if you really want a mutating counterpart for .forEach method, use an extension on MutableCollection :

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 }

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM