Swift Tip: In-Place Map
When we find ourselves repeating code on both the left-hand and right-hand side of an assignment operator, it is often a hint that we could be using a mutating method instead (for example, we could use toggle
on Bool
).
Consider the following example:
struct User {
var favorites: [String]
}
var users = [
"ida": User(favorites: ["https://www.objc.io"])
]
Now let's say we want to change the favorites for ida
: we'd like to replace each "https" with "http". It's not uncommon to see code like this:
// Check if the key is present
if var u = users["ida"] {
// Modify a nested property
u.favorites = u.favorites.map {
$0.replacingOccurrences(of: "https", with: "http")
}
// Re-assign the new value
users["ida"] = u
}
There are a few things that are tricky in the code above: we need to make sure that the key matches in both the lookup and the assignment, and the repetition of favorites
in the assignment is also not so nice. Ideally, we'd write this in a single line:
users["ida"]?.favorites = (users["ida"].favorites ?? []).map { /* ... */ }
This still has duplication on either side of the assignment operator, and the nil coalescing is distracting from what we're trying to say. Instead, we could define a version of map
that mutates the array in place:
extension Array {
mutating func mapInPlace(_ transform: (Element) -> Element) {
self = map(transform)
}
}
Now we can write our code without duplication:
users["ida"]?.favorites.mapInPlace {
$0.replacingOccurrences(of: "https", with: "http")
}
If you like this approach, you can make it available to many more types by writing mapInPlace
on MutableCollection
. If you like mutation, the transform
could be written in inout
-style as well.
In Swift Talk 21 (a public episode), we look at other ways to work with structs. Our book, Advanced Swift, covers mutating
, inout
, and value types in depth.
Subscribers make our public episodes possible.
To support us, you can subscribe too.