Sorting arrays is a common task in Swift. In this episode, you learn a handful of patterns to sort arrays, including sorting arrays of objects and sorting arrays by property. We use several functions that are defined in by Swift's Standard Library, such as sort and sorted. Are you ready?

The Basics

Let's start by creating a playground in Xcode. Add an import statement for the Foundation framework at the top.

import Foundation

We start simple with an array of integers based on the Fibonacci sequence. We store the array of Ints in an constant with name numbers.

import Foundation

var numbers = [2, 144, 3, 5, 89, 13, 21, 1, 34, 8, 1, 55, 0]

We have two options to sort the array in Swift, sorted() and sort(). The sorted()method returns an sorted copy of the array whereassort()mutates the array of numbers. Sosorted()returns a copy whereassort()` is a mutating method or function.

import Foundation

var numbers = [2, 144, 3, 5, 89, 13, 21, 1, 34, 8, 1, 55, 0]

let sortedNumbers = numbers.sorted() // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
numbers.sort() // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]

This implies that the array you want to sort needs to be mutable (a variable) if you want to use the sort() method. The compiler kindly reminds you if you forget this detail.

How to Sort an Array in Swift

The Comparable Protocol

You may be wondering how the sorted() and sort() methods know how to sort the array of numbers. The type of the elements of the array need to conform to the Comparable protocol. The elements are sorted in ascending order.

Many Foundation types conform to the Comparable protocol, including the String struct. In this example, we sort an array of coffee beverages.

import Foundation

var beverages = ["Espresso", "Cappuccino", "Latte", "Americano", "Mocha"]

let sortedBeverages = beverages.sorted() // ["Americano", "Cappuccino", "Espresso", "Latte", "Mocha"]
beverages.sort() // ["Americano", "Cappuccino", "Espresso", "Latte", "Mocha"]

Sort Array in Descending Order

To sort an array in descending order, you invoke sorted(by:) method and pass in the sort order, the less-than operator (<) for ascending order and the greater-than operator (>) for descending order.

import Foundation

var numbers = [2, 144, 3, 5, 89, 13, 21, 1, 34, 8, 1, 55, 0]

let ascendingNumbers = numbers.sorted(by: <) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
let descendingNumbers = numbers.sorted(by: >) // [144, 89, 55, 34, 21, 13, 8, 5, 3, 2, 1, 1, 0]

Sorting an Array of Objects

Let's make it a bit more interesting and sort an array of objects. We first declare a simple struct we can work with.

import Foundation

struct Movie {

    // MARK: - Properties

    let title: String
    let releaseDate: Int

}

We create an array of movies using the Movie struct. I'm a fan of Harry Potter so ...


let movies = [
    Movie(title: "Harry Potter and the Sorcerer's Stone", releaseDate: 2001),
    Movie(title: "Harry Potter and the Chamber of Secrets", releaseDate: 2002),
    Movie(title: "Harry Potter and the Prisoner of Azkaban", releaseDate: 2004),
    Movie(title: "Harry Potter and the Goblet of Fire", releaseDate: 2005),
    Movie(title: "Harry Potter and the Order of the Phoenix", releaseDate: 2007),
    Movie(title: "Harry Potter and the Half-Blood Prince", releaseDate: 2009),
    Movie(title: "Harry Potter and the Deathly Hallows – Part 1", releaseDate: 2010),
    Movie(title: "Harry Potter and the Deathly Hallows – Part 2", releaseDate: 2011),
]

If we try to sort the array using the sorted() and sort() methods, we run into a compiler error. Can you guess why that is? As I wrote earlier, the type of the elements of the array needs to conform to the Comparable protocol.

As an alternative, we can sort the array by passing a closure to the sorted(by:) method. In the closure, we define how the elements of the array need to be sorted. In this example, we sort the movies by the Movie struct's releaseDate property in descending order.

movies.sorted(by: { movie1, movie2 in
    movie1.releaseDate < movie2.releaseDate
})

We can shorten the implementation by using trailing closure syntax.

movies.sorted { movie1, movie2 in
    movie1.releaseDate > movie2.releaseDate
}

We can shorten the implementation even more by using shorthand argument names.

movies.sorted { $0.releaseDate > $1.releaseDate }

If we declare movies as a variable, we can also use the sort(by:) method.

var movies = [
    Movie(title: "Harry Potter and the Sorcerer's Stone", releaseDate: 2001),
    Movie(title: "Harry Potter and the Chamber of Secrets", releaseDate: 2002),
    Movie(title: "Harry Potter and the Prisoner of Azkaban", releaseDate: 2004),
    Movie(title: "Harry Potter and the Goblet of Fire", releaseDate: 2005),
    Movie(title: "Harry Potter and the Order of the Phoenix", releaseDate: 2007),
    Movie(title: "Harry Potter and the Half-Blood Prince", releaseDate: 2009),
    Movie(title: "Harry Potter and the Deathly Hallows – Part 1", releaseDate: 2010),
    Movie(title: "Harry Potter and the Deathly Hallows – Part 2", releaseDate: 2011),
]

movies.sort { $0.releaseDate > $1.releaseDate }

Conforming to the Comparable Protocol

If you do a lot of sorting in your app, then it is much more convenient to conform the type your are sorting to the Comparable protocol. This requires two steps. First, conform the Movie struct to the Comparable protocol. Second, implement the less-than operator (<) and the equal-to operator (==). We only need to implement the less-than operator. Swift implements the equal-to operator for us.

import Foundation

struct Movie: Comparable {

    // MARK: - Comparable

    static func < (lhs: Movie, rhs: Movie) -> Bool {
        lhs.releaseDate > rhs.releaseDate
    }

    // MARK: - Properties

    let title: String
    let releaseDate: Int

}

With the Movie struct conforming to the Comparable protocol, we can shorten the implementation even more.

movies.sort()

Advanced: Using a SortComparator

Apple added one other option to sort collections a few years ago, the SortComparator protocol. This protocol is available as of iOS 15.0, tvOS 15.0, macOS 12.0, and watchOS 8.0. It is a more advanced option, but it can be powerful and convenient. I like how a sort comparator encapsulates the nitty-gritty details of the sorting logic.

Declare a struct with name MovieSortComparator that conforms to the SortComparator protocol.

struct MovieSortComparator: SortComparator {

}

The SortComparator protocol has a few requirements. We first defined a type alias for Compared, the type of the elements of the collection we want to sort.

struct MovieSortComparator: SortComparator {

    // MARK: - Type Aliases

    typealias Compared = Movie

}

We also need to declare a stored property with name order of type SortOrder. As the name suggests, the order property defines the sort order of the sorted collection after the sort comparator has done its work.

struct MovieSortComparator: SortComparator {

    // MARK: - Type Aliases

    typealias Compared = Movie

    // MARK: - Properties

    var order: SortOrder

}

The magic happens in the compare(_:_:) method. It accepts two elements of the collection and its return type is ComparisonResult. The possible values are orderedSame, orderedAscending, and orderedDescending.

struct MovieSortComparator: SortComparator {

    // MARK: - Type Aliases

    typealias Compared = Movie

    // MARK: - Properties

    var order: SortOrder

    // MARK: - Methods

    func compare(_ lhs: Movie, _ rhs: Movie) -> ComparisonResult {
    	
	}

}

In the body of the compare(_:_:) method, we use a guard statement to return early if the release dates of both movies are the same. In that scenario, we return orderedSame.

struct MovieSortComparator: SortComparator {

    // MARK: - Type Aliases

    typealias Compared = Movie

    // MARK: - Properties

    var order: SortOrder

    // MARK: - Methods

    func compare(_ lhs: Movie, _ rhs: Movie) -> ComparisonResult {
        guard lhs.releaseDate != rhs.releaseDate else {
            return .orderedSame
        }
    }

}

In the remainder of the implementation, we compare the release dates of the movies. Note that we take the value of the order property into account to determine the return value.

struct MovieSortComparator: SortComparator {

    // MARK: - Type Aliases

    typealias Compared = Movie

    // MARK: - Properties

    var order: SortOrder

    // MARK: - Methods

    func compare(_ lhs: Movie, _ rhs: Movie) -> ComparisonResult {
        guard lhs.releaseDate != rhs.releaseDate else {
            return .orderedSame
        }

        if lhs.releaseDate > rhs.releaseDate {
            if order == .forward {
                return .orderedDescending
            } else {
                return .orderedAscending
            }
        } else {
            if order == .forward {
                return .orderedAscending
            } else {
                return .orderedDescending
            }
        }
    }

}

Using the MovieSortComparator struct is very easy. We create a MovieSortComparator object, set its order property through the initializer, and pass it to the sorted(using:) method. That's it.

let sortComparator = MovieSortComparator(order: .forward)
movies.sorted(using: sortComparator)

The SortComparator protocol is quite powerful and it makes it very easy to (1) centralize sort logic and (2) unit test sort logic. For example, you could add a property to MovieSortComparator to define the property to use for the sorting operation. In the compare(_:_:) method, you would then inspect the value of the key property. This makes the sort comparator much more flexible.

struct MovieSortComparator: SortComparator {

    // MARK: - Types

    enum Key {

        // MARK: - Cases

        case title
        case releaseDate
    }

    // MARK: - Type Aliases

    typealias Compared = Movie

    // MARK: - Properties

    let key: Key

    // MARK: -

    var order: SortOrder

    // MARK: - Methods

    func compare(_ lhs: Movie, _ rhs: Movie) -> ComparisonResult {
        switch key  {
        case .title:
            // ...
        case .releaseDate:
            // ...
        }
	}

}

This is what it looks like at the call site. This looks pretty neat. Right?

let sortComparator = MovieSortComparator(
    key: .title,
    order: .forward
)

movies.sorted(using: sortComparator)

What's Next?

In most situations, you find yourself using the sorted(by:) and sort(by:) methods. The SortComparator protocol is very useful, but it is a more advanced approach to sort collections. I encourage you to explore it if you find you are repeating yourself in various places of your codebase.