Functional programming is often done in Swift and so easy that it could easily hit performance. Iterating over large collections and performing actions like filter
or map
is common and should be used wisely.
Performance is often decreasing when these methods are combined, like a filter
followed by first
. A list of best practices.
Prefer contains
over first(where:) != nil
Verifying if an object exists in a collection can be done in multiple ways. Make sure to use contains
for best performance.
Best practice
let numbers = [0, 1, 2, 3]
numbers.contains(1)
Bad practice
let numbers = [0, 1, 2, 3]
numbers.filter { number in number == 1 }.isEmpty == false
numbers.first(where: { number in number == 1 }) != nil
Prefer checking isEmpty
over comparing count
to zero.
The best way to describe the reasoning for this is by quoting the isEmpty documentation.
When you need to check whether your collection is empty, use the isEmpty property instead of checking that the count property is equal to zero. For collections that don’t conform to RandomAccessCollection, accessing the count property iterates through the elements of the collection.
Best practice
let numbers = []
numbers.isEmpty
Bad practice
let numbers = []
numbers.count == 0
Checking for an empty String with isEmpty
A String
in Swift can be seen as a collection of Characters. This means that the above performance practice counts for strings as well and isEmpty
should be used to check for an empty string.
Best practice
myString.isEmpty
Bad practice
myString == ""
myString.count == 0
Get the first object matching a condition
Iterating over a collection to get the first object matching a given condition can be done using a filter
following by first
, but best practice is to use first(where:)
instead.
The latter stops iterating over the collection as soon as the condition is met for the first time. The filter
would continue iterating over the whole collection, even though it might already hit an object matching the condition.
Obviously, the same counts for last(where:)
.
Best practice
let numbers = [3, 7, 4, -2, 9, -6, 10, 1]
let firstNegative = numbers.first(where: { $0 < 0 })
Bad practice
let numbers = [3, 7, 4, -2, 9, -6, 10, 1]
let firstNegative = numbers.filter { $0 < 0 }.first
Getting the minimum or maximum element in a collection
The minimum element in a collection can be found after sorting it and selecting the first object. The same counts for the maximum element. This, however, requires to sort the whole array first. Using the min()
and max()
methods is best practice in this case.
Best practice
let numbers = [0, 4, 2, 8]
let minNumber = numbers.min()
let maxNumber = numbers.max()
Bad practice
let numbers = [0, 4, 2, 8]
let minNumber = numbers.sorted().first
let maxNumber = numbers.sorted().last
Making sure all objects match a condition
It’s easy to use a filter
combined with isEmpty
to verify that all objects in a collection match a given condition. With SE-0207 introduced in Swift 4.2 we can now use allSatisfy(_:)
to verify that every element of a collection matches a given condition.
Best practice
let numbers = [0, 2, 4, 6]
let allEven = numbers.allSatisfy { $0 % 2 == 0 }
Bad practice
let numbers = [0, 2, 4, 6]
let allEven = numbers.filter { $0 % 2 != 0 }.isEmpty
Using SwiftLint to ensure best practices
Most of these examples are implemented in SwiftLint and help you to ensure that these best practices are used.