Swift's Array
type is powerful and flexible. In this post, I show you a simple solution to find an object in an array. Fire up Xcode and create a playground if you want to follow along. Clear the contents of the playground and add an import statement for Foundation.
import Foundation
We start simple and define an array of integers. Notice that the array contains a few duplicates.
import Foundation
let numbers = [
1,
2,
3,
4,
5,
1,
2,
3
]
Swift 5
The API of Array
has changed quite a bit over the years. This post uses Swift 5.3 and I won't cover any of the deprecated methods.
First Where
The Swift Standard Library defines a number of methods on Array
to browse the items in an array. Let's take a look at the most obvious option first, the first(where:)
method.
The method accepts a closure as its only argument. The closure accepts an object of type Int
and its return value is Bool
. The idea is simple. The first(where:)
method returns the first item in the array for which the closure returns true
. Let's try it out.
We want to find the first integer that is larger than 3
. This is what that looks like in code.
let result = numbers.first(where: { value -> Bool in
value > 3
})
We can improve the implementation by using Swift's trailing closure syntax.
let result = numbers.first { value -> Bool in
value > 3
}
We can further simplify the implementation by making use of shorthand argument syntax. This looks much better.
let result = numbers.first { $0 > 3 }
It is important to know that the return type of the first(where:)
method is of an optional type. It returns nil
if the closure that is passed to the first(where:)
method returns false
for every item of the array. In the following example, result
is equal to nil
because none of the items of the array are greater than 10
.
let result = numbers.first { $0 > 10 }
Finding an Object in an Array
Even though the previous example uses an array of integers, the first(where:)
method can be used for any type of array. Take a look at the following example. We create an array of User
objects and use the first(where:)
method to find the first user with a first name equal to Emma
.
import Foundation
struct User {
let first: String
let last: String
}
let users = [
User(first: "Emma", last: "Jones"),
User(first: "Mike", last: "Thompson"),
User(first: "Lucy", last: "Johnson"),
User(first: "James", last: "Wood"),
User(first: "Cathy", last: "Miller")
]
let result = users.first { $0.first == "Emma" } // Emma Jones
Filtering Objects of an Array
The first(where:)
method returns a single object, or nil
if no object was found. If you need every object that matches a condition, then the filter(_:)
method is what you need. In the following example, we filter the array of users to only include users whose last name starts with the letter J. Two users match that condition.
import Foundation
struct User {
let first: String
let last: String
}
let users = [
User(first: "Emma", last: "Jones"),
User(first: "Mike", last: "Thompson"),
User(first: "Lucy", last: "Johnson"),
User(first: "James", last: "Wood"),
User(first: "Cathy", last: "Miller")
]
let result = users.filter { $0.last.starts(with: "J") } // Emma Jones, Lucy Johnson
The return type of the filter(_:)
method isn't of an optional type. It returns an array. If none of the objects of the array match the condition, the returned array is empty.
The syntax of the filter(_:)
method is very similar to that of the first(where:)
method. The filter(_:)
method accepts a closure as its only argument. The closure accepts an object of type Int
and its return value is Bool
. The resulting array includes every object of the array for which the closure returns true
.
Swift Performance
Like many other programming languages, Swift includes a wide range of optimizations to improve performance. Let's revisit the second example.
import Foundation
struct User {
let first: String
let last: String
}
let users = [
User(first: "Emma", last: "Jones"),
User(first: "Mike", last: "Thompson"),
User(first: "Lucy", last: "Johnson"),
User(first: "James", last: "Wood"),
User(first: "Cathy", last: "Miller")
]
let result = users.first { $0.first == "Emma" } // Emma Jones
The closure that is passed to the first(where:)
method is executed once. Why is that? The closure that is passed to the first(where:)
method returns true
for the first User
object of the array. Because the first(where:)
method returns a single object and it found a match, it makes no sense to execute the closure for the remaining objects of the array. This is subtle but important detail.
Last Where
Swift also has a convenient method for finding the last element of an array that matches a given condition. The syntax is identical to that of the first(where:)
method. The difference is, as you may have guessed, that the last element of the array for which the closure return true
is returned. Take a look at this example.
import Foundation
struct User {
let first: String
let last: String
}
let users = [
User(first: "Emma", last: "Jones"),
User(first: "Mike", last: "Thompson"),
User(first: "Lucy", last: "Johnson"),
User(first: "James", last: "Wood"),
User(first: "Cathy", last: "Miller")
]
let result = users.last { $0.first == "Lucy" } // Lucy Johnson
Be Careful
Always be careful when using first(where:)
and last(where:)
. The return types of these methods are of an optional type. These methods return nil
if none of the elements of the array match the given condition.