I'm using swift for my project.
I have an array of structs named Instrument
. Later on I made a function that returns specific Instrument
from array. Then I wanted to change value on one of its property, but this change is not reflected inside the array.
I need to have this array to include all the changes on the elements inside. What do you think is the best practice here?
Instrument
from struct
to class
.Instrument
from array.Right now I use this function:
func instrument(for identifier: String) -> Instrument? {
if let instrument = instruments.filter({ $0.identifier == identifier }).first {
return instrument
}
return nil
}
I start with the struct because swift is known to be language for structs and I want to learn when to use struct
of class
.
thanks
To change the value of a specific element of an array, we refer to its index number or position in the given array. Remember that in C++, the first character or element of a given object has its index position or number as 0 .
Unlike classes, a constant struct's properties cannot be changed—not from outside the struct, not even from within the struct's own methods, even if they're marked as mutating . Once a struct is constant, it is constant. It can't change.
C = struct2cell( S ) converts a structure into a cell array. The cell array C contains values copied from the fields of S . The struct2cell function does not return field names. To return the field names in a cell array, use the fieldnames function.
Even though we defined x within the struct as a var property, we cannot change it, because origin is defined using let . This has some major advantages. For example, if you read a line like let point = ... , and you know that point is a struct variable, then you also know that it will never, ever, change.
With an array of struct Instrument, you can obtain the index for the Instrument with a particular identifier, and use that to access and modify a property of the Instrument.
struct Instrument {
let identifier: String
var value: Int
}
var instruments = [
Instrument(identifier: "alpha", value: 3),
Instrument(identifier: "beta", value: 9),
]
if let index = instruments.index(where: { $0.identifier == "alpha" }) {
instruments[index].value *= 2
}
print(instruments) // [Instrument(identifier: "alpha", value: 6), Instrument(identifier: "beta", value: 9)]
If you stick to the value type approach (and given that the identifier
is not unique: otherwise, consider using a dictionary for simple extract-and-replace logic), you could write a mutating function to the type which owns the [Instruments]
array, which finds a (first) Instrument
instance in the array and mutates it using a supplied closure. E.g. (thanks @Hamish for improvements!):
struct Instrument {
let identifier: String
var changeThis: Int
init(_ identifier: String, _ changeThis: Int) {
self.identifier = identifier
self.changeThis = changeThis
}
}
struct Foo {
var instruments: [Instrument]
@discardableResult // do not necessarily make use of the return result (no warning if not)
mutating func updateInstrument(forFirst identifier: String,
using mutate: (inout Instrument) -> ()) -> Bool {
if let idx = instruments.indices
.first(where: { instruments[$0].identifier == identifier }) {
// mutate this instrument (in-place) using supplied closure
mutate(&instruments[idx])
return true // replacement successful
}
return false // didn't find such an instrument
}
}
Example usage:
var foo = Foo(instruments:
[Instrument("a", 1), Instrument("b", 2),
Instrument("c", 3), Instrument("b", 4)])
// make use of result of call
if foo.updateInstrument(forFirst: "b", using: { $0.changeThis = 42 }) {
print("Successfully mutated an instrument")
} // Successfully mutated an instrument
// just attempt mutate and discard the result
foo.updateInstrument(forFirst: "c", using: { $0.changeThis = 99 })
print(foo.instruments)
/* [Instrument(identifier: "a", changeThis: 1),
Instrument(identifier: "b", changeThis: 42),
Instrument(identifier: "c", changeThis: 99),
Instrument(identifier: "b", changeThis: 4)] */
As shown in @Owen:s answer, an even neater approach to finding the first index for a certain predicate on the element is using the index(where:)
method of array (rather than indices.first(where:)
as used above). Using the index(where:)
approach in the complete example above would simply correspond to replacing
if let idx = instruments.indices
.first(where: { instruments[$0].identifier == identifier }) { ...
with
if let idx = instruments
.index(where: { $0.identifier == identifier }) { ...
in the updateInstrument(forFirst:using)
method of Foo
.
We could further condense the updateInstrument(forFirst:using)
method by applying the map
function of Optional
to perform the (possible) replacement and boolean return in a single line:
struct Foo {
var instruments: [Instrument]
@discardableResult
mutating func updateInstrument(forFirst identifier: String,
using mutate: (inout Instrument) -> ()) -> Bool {
return instruments
.index(where: { $0.identifier == identifier })
.map { mutate(&instruments[$0]) } != nil
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With