Swift - How to mutate a struct object when iterating over it
struct
are value types, thus in the for
loop you are dealing with a copy.
Just as a test you might try this:
Swift 3:
struct Options {
var backgroundColor = UIColor.black
}
var arrayOfMyStruct = [Options]()
for (index, _) in arrayOfMyStruct.enumerated() {
arrayOfMyStruct[index].backgroundColor = UIColor.red
}
Swift 2:
struct Options {
var backgroundColor = UIColor.blackColor()
}
var arrayOfMyStruct = [Options]()
for (index, _) in enumerate(arrayOfMyStruct) {
arrayOfMyStruct[index].backgroundColor = UIColor.redColor()
}
Here you just enumerate the index, and access directly the value stored in the array.
Hope this helps.
Swift and mutating struct
The mutability attribute is marked on a storage (constant or variable), not a type. You can think struct has two modes: mutable and immutable. If you assign a struct value to an immutable storage (we call it let
or constant in Swift) the value becomes immutable mode, and you cannot change any state in the value. (including calling any mutating method)
If the value is assigned to a mutable storage (we call it var
or variable in Swift), you're free to modify the state of them, and calling of mutating method is allowed.
In addition, classes don't have this immutable/mutable mode. IMO, this is because classes are usually used to represent reference-able entity. And reference-able entity is usually mutable because it's very hard to make and manage reference graphs of entities in immutable manner with proper performance. They may add this feature later, but not now at least.
For Objective-C programmers, mutable/immutable concepts are very familiar. In Objective-C we had two separated classes for each concept, but in Swift, you can do this with one struct. Half work.
For C/C++ programmers, this is also very familiar concept. This is exactly what const
keyword do in C/C++.
Also, immutable value can be very nicely optimised. In theory, Swift compiler (or LLVM) can perform copy-elision on values passed by let
, just like in C++. If you use immutable struct wisely, it will outperform refcounted classes.
Update
As @Joseph claimed this doesn't provide why, I am adding a little more.
Structs have two kind of methods. plain and mutating methods. Plain method implies immutable (or non-mutating). This separation exists only to support immutable semantics. An object in immutable mode shouldn't change its state at all.
Then, immutable methods must guarantee this semantic immutability. Which means it shouldn't change any internal value. So compiler disallows any state changes of itself in a immutable method. In contrast, mutating methods are free to modify states.
And then, you may have a question of why immutable is the default? That's because it's very hard to predict the future state of mutating values, and that usually becomes the main source of headaches and bugs. Many people agreed that the solution is avoiding mutable stuffs, and then immutable by default was on top of wish list for decades in C/C++ family languages and its derivations.
See purely functional style for more details. Anyway, we still need mutable stuffs because immutable stuffs have some weaknesses, and discussing about them seems to be out of topic.
I hope this helps.
Change Values while Iterating Over Subarrays in Swift
You can iterate over the indices of the outer array. Then you can read and write the inner arrays using the indices:
for index in coordinatesList.indices {
if coordinatesList[index][0] == 6 {
coordinatesList[index][3] = 5
}
}
Swift iOS -How come I can loop through an array of class objects and make property changes but not structs
The issue is caused by the fact that struct
s 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, class
es 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
}
how do people deal with iterating a Swift struct value-type property?
As of Swift 4, a compromise is to iterate over the indices of a mutable collection instead of the elements themselves, so that
for elem in mutableCollection {
// `elem` is immutable ...
}
or
for var elem in mutableCollection {
// `elem` is mutable, but a _copy_ of the collection element ...
}
becomes
for idx in mutableCollection.indices {
// mutate `mutableCollection[idx]` ...
}
In your example:
for idx in f.columns.indices {
f.columns[idx].cards.append(Card())
}
As @Hamish pointed out in the comments, a future version of Swift may implement a mutating iteration, making
for inout elem in mutableCollection {
// mutate `elem` ...
}
possible.
Swift struct mutating a variable not working?
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.
Iterate and modify Struct in a Set
You can assign mySet
as mapped mySet
converted to Set
mySet = Set(mySet.map { $0 + 1 })
Why mutating function next does not change the struct (conforming to Sequence and IteratorProtocol) after the iteration?
Your for ... in
-Loop works on a copy of stack and never changes the stack itself. If you were to call next()
yourself, the pop()
would modify the stack as you can see here:
import Foundation
struct Stack<Element> {
var store: [Element] = []
mutating func push(_ element:Element) {
store.append(element)
}
mutating func pop() -> Element? {
return store.popLast()
}
}
extension Stack: Sequence, IteratorProtocol {
mutating func next() -> Element? {
return pop()
}
}
var stack = Stack<Int>()
stack.push(1)
stack.push(2)
stack.push(3)
for s in stack {
print(s)
}
stack.next()
print(stack.store)
Output:
3
2
1
[1, 2]
However as @user3581248 pointed out in the comments, making Stack a class instead of a struct (and removing mutating
from its functions) gives you the desired behavior.
Related Topics
Finish Asynchronous Task in Firebase With Swift
Swift: Print Decimal Precision of Division
Hide Navigation Bar Without Losing Swipe Back Gesture in Swiftui
Obervableobject Being Init Multiple Time, and Not Refreshing My View
Swift Dictionary Default Value
How to Make Window Transparent in Osx Swift
Uploading Image to Firebase Storage and Database
Replacing Calayer and Cabasicanimation with Skscene and Skactions
Swift Spritekit Adding Button Programmatically
Xcode 7.3.1 Hangs on "Copying Swift Standard Libraries"
How to Load an Image from Documents Directory on MACos Swift
Swift Protocol Error: 'Weak' Cannot Be Applied to Non-Class Type
Determine If Any.Type Is Optional
Simpliest Solution to Check If File Exists on a Webserver. (Swift)
How to Change Back Button Title on Navigation Controller in Swift3