Published on September 17, 2020
Byte of the Week: Lazy Collections
It can be tricky to optimize operations when working with sequences. One way to achieve better performance with collections in Swift is to use their lazy counterpart to perform operations such as mapping and filtering.
Lazy collections are collections where the results of some operations are combined into a single value, thus avoiding creating multiple intermediary collections. For example, if you call map
twice on an array, a lazy collection will combine the results of these calls into one array, instead of returning an array as the result of the first map
, using that array in the second map
call, and then returning a second array.
You create a lazy collection by calling lazy
on the base collection. To understand if a lazy collection is beneficial to your use case, you should remember the distinction between these two categories of operations:
- Lazy operations: operations that can be performed in place and that do not affect the order of the elements:
map
flatMap
compactMap
filter
joined
prefix(while:)
drop(while:)
- Eager operations: all the other operations that must return an intermediary collection to ensure data consistency, or that convert a lazy collection to a normal collection.
The code behind lazy operations is not called until just before an eager operation is performed.
Caveats
- You cannot use
Int
indices directly by subscripting on a filter lazy collection because it defers subscripting to the base collection. Useindex(_:offsetBy:)
instead, like you would for a String. [1]
Example
You typically should use a lazy collection if you perform one or more lazy operations followed by an eager operation. A good example is if you need to use both a filter and a transformation:
let users = [
"High School Musical": [
User(name: "Troy", age: 17),
User(name: "Gabriella", age: 16),
User(name: "Sharpay", age: 18),
User(name: "Ryan", age: 18)
],
"Wizards of Waverly Place": [
User(name: "Justin", age: 19),
User(name: "Alex", age: 17),
User(name: "Harper", age: 17),
User(name: "Max", age: 14)
]
]
let adults = users
.values
.lazy
.joined()
.filter {
$0.age >= 18
}
.sorted {
$0.name < $1.name
} // joined and filter are not called until sorted is called.