0

If I loop through an array of Class objects I can make changes to a property on it

class Country {
    var name: String?
    var region: String?

    init(name: String?, region: String?) {
        self.name = name
        self.region = region
    }
}

let canada = Country(name: "Canada", region: "North America")
let mexico = Country(name: "Mexico", region: "North Ameria")
let france = Country(name: "France", region: "Europe")
let korea = Country(name: "Korea", region: "Asia")

var countryArr = [canada, mexico, france, korea]

// this works fine
let transformed = countryArr.map { $0.name = "Random" }

But if I try this with Struct objects I get

Cannot assign to property: '$0' is immutable

struct Country {
    var name: String?
    var region: String?
}

var canada = Country(name: "Canada", region: "North America")
var mexico = Country(name: "Mexico", region: "North Ameria")
var france = Country(name: "France", region: "Europe")
var korea = Country(name: "Korea", region: "Asia")

var countryArr = [canada, mexico, france, korea]

// this gets an error
let transformed = countryArr.map { $0.name = "Random" }
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256

1 Answers1

0

The issue is caused by the fact that structs are value types, so mutating any properties of the struct mutates the struct instance itself as well and the closure input arguments in map are immutable. So when you try to mutate a property $0 in the closure of map, you are trying to mutate $0 itself in case map is called on a collection of value types.

On the other hand, classes are reference types, so mutating a property of a class instance doesn't mutate the instance itself.

A solution for your problem is to create a mutable copy of the struct instance in the map, mutate its name property and return that. There are two solutions, if you have a small number of properties on your type, calling its memberwise initialiser is easier, but if you have a lot of properties and only want to mutate a few, copying the struct and then modifying the necessary properties is the better choice.

let transformed = countryArr.map { Country(name: "Random", region: $0.region) }
let transformed2 = countryArr.map { country->Country in
    var copy = country
    copy.name = "Random"
    return copy
}
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
  • Hey thanks for the explanation. I can’t upvote for 2 minutes so wait a few. – Lance Samaria Mar 15 '19 at 16:14
  • `let transformed = countryArr.indices.map { countryArr[$0].name = "Random" }` also should work – E.Coms Mar 15 '19 at 16:17
  • @E.Coms if `countryArr` is mutable, it does work, but the other two solutions work even if the array itself is immutable. The first solution even works if the properties themselves are immutable. – Dávid Pásztor Mar 15 '19 at 16:19
  • @DávidPásztor So using .map {$0 } on a struct will actually attempt to change the memory address of the original struct or will it try to create a new struct at a new memory address? – Lance Samaria Mar 19 '19 at 16:05
  • @LanceSamaria Simply calling `.map{$0}` on an Array of structs most probably won't do either, since `Array` implements copy-on-write optimization, so unless you try to mutate an element of the array, it won't actually be copied, but will be passed by reference. So it will do neither of those. – Dávid Pásztor Mar 19 '19 at 16:13
  • @DávidPásztor sorry about that, I meant arr.map { $0.property = “changeToSomeValue” }. I don’t see the difference between that and just changing the value on the property itself: var x = SomeStruct(); x.property = “David”; then later x.property = “Lance”. Unless your saying using .map in an array to change a struct alters the struct itself and not it’s property that’s why it won’t work – Lance Samaria Mar 19 '19 at 17:16
  • 1
    @LanceSamaria your example wouldn't actually compile, since as I've already stated in my answer, the closure input argument is immutable. As I've also already stated, structs are value types, so changing a struct property does change the struct instance itself as well – Dávid Pásztor Mar 19 '19 at 17:32
  • Ok I got it. That’s the part is was lost. Much appreciated! – Lance Samaria Mar 19 '19 at 17:37