This question already has answers here:
Closed 2 years ago.
I am not able to modify my model class variable even using mutating func
keyword in a method?
So basically I have wrapped my problem in a very easy way I have a class Car
that has 3 variable id
, start
, and modelNo
After that initialize an empty Car model array and then I want to show 10 cars, creating a range for loop which iterates 1...10
, initializing the Car model class and appending it to original cars array.
There is a check for first 5 cars id will be 0 and last 5 cars id will be 1
I want a filter in which same id's last car will start so I have created a method filtered and modifying the start variable but not able to modify it. Can you please help me what I am doing wrong?
Please see my complete code and copy paste it to the playground.
struct Car {
var id = 0
var start = false
var modelNo: String
init(start: Bool, id: Int, model: String) {
self.start = start
self.id = id
self.modelNo = model
}
mutating func startCar(status: Bool) {
start = status
}
}
var arrCars:[Car] = [Car]()
for i in 1...10 {
let md = Car(start: false, id: i <= 5 ? 0 : 1, model: "Model\(i)")
arrCars.append(md)
}
private func filtered() {
for item in arrCars {
let filteredItems = arrCars.filter { $0.id == item.id }
if filteredItems.count > 0 {
if var lastItem = filteredItems.last {
print("Will start \(lastItem.modelNo)")
lastItem.startCar(status: true)
}
}
}
let cars = arrCars.filter { (car) -> Bool in
car.start == true
}
print(cars.count)
}
filtered()
Any help will be appreciated.
Creating a mutating
function on a struct doesn't change the value semantics of structs. As soon as a struct instance is mutated, a copy is made.
You say:
if var lastItem = filteredItems.last {
print("Will start \(lastItem.modelNo)")
lastItem.startCar(status: true)
}
So, lastItem
contains an instance of a car you are going to start. Under the covers this is the same instance of the car that is in filteredItems
, which is the same instance that is in arrCars
. But, as soon as you mutate the Car
in lastItem
, a copy is made, so lastItem
no longer has the same instance as the one in arrCars
. filteredItems
and arrCars
still contain the original, unaltered Car
instance.
You can change Car
to be a class
rather than a struct
so that it has reference semantics to get the behaviour you want.
The other option is to modify the instance in place; something like arrCars[0].startCar(status: true)
. To do this you will need to get an array containing the indicies of the cars you want to start rather than the cars themselves:
private func filtered() {
for item in arrCars {
let matchingIndices = arrCars.enumerated().compactMap { (index,car) in
return car.id == item.id ? index:nil
}
if matchingIndices.count > 0 {
if let lastIndex = matchingIndices.last {
print("Will start \(arrCars[lastIndex].modelNo)")
arrCars[lastIndex].startCar(status: true)
}
}
}
let cars = arrCars.filter { (car) -> Bool in
car.start == true
}
print(cars.count)
}
However, where a model object requires mutability or is stateful, a struct is probably not suitable.
The answers you've gotten so far are not wrong, but they seem a little confusing. Let's look at it a little more simply. The problem is this line:
if var lastItem = filteredItems.last
This yields a Car as lastItem
. But it is not the same Car as the one in the filteredItems
.
That is because a struct is a value class. Assignment copies the struct. Thus, what you do to lastItem
has no effect on what is sitting inside filteredItems
.
So, you are not having any trouble mutating a struct. You are mutating lastItem
just fine! The problem is that the lastItem
Car is not the same object as any Car inside filteredItems
.
Firstly try to run this code and check if it solves the problem if we stick to Structure
s. Then I'll try to explain why this helps:
struct Car {
var id = 0
var start = false
var modelNo: String
init(start: Bool, id: Int, model: String) {
self.start = start
self.id = id
self.modelNo = model
}
mutating func startCar(status: Bool) {
start = status
}
}
var arrCars: Array<Car> = [Car]()
for i in 1...10 {
let md = Car(start: false, id: i <= 5 ? 0 : 1, model: "Model\(i)")
arrCars.append(md)
}
private func filtered() {
for item in arrCars {
let filteredItems = arrCars.filter { $0.id == item.id }
if filteredItems.count > 0 {
// solves the problem
arrCars[filteredItems.count-1].startCar(status: true)
print("Will start \(arrCars[filteredItems.count-1].modelNo)")
}
}
let cars = arrCars.filter { (car) -> Bool in
car.start == true
}
print(cars.count)
}
filtered()