Swift struct mutating a variable not working? [dup

2020-07-28 11:40发布

问题:

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.

回答1:

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.



回答2:

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.



回答3:

Firstly try to run this code and check if it solves the problem if we stick to Structures. 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()