Swift Equatable, Comparable, Hashable, and Identifiable Guide

Swift Equatable, Comparable, Hashable, and Identifiable Guide

iOSSwift
Fix bugs faster! Log Collection Made Easy
START NOW!

There are several protocols featured in the Swift Standard Library that enable generic programming in Swift. By defining common behaviors and functionalities for types and collections, these protocols provide a standard way for types to interact with each other.

Today we’re going to take a look at four of the key Swift Standard Library Protocols, these are:

  • Equatable
  • Comparable
  • Hashable
  • Identifiable

We’ll provide a brief overview of how each works, and then how we can implement them in our projects.

Ok, let’s get started…

Swift Equatable

The most straightforward of the four protocols we’ll be looking at, Equatable (as the name suggests) is for comparing types for equality, providing a simple way to see if two types are the same.

So, what’s under the hood?

A quick look at the declaration in Swift really underscores how straightforward Equatable can be, as we can see below:

public protocol Equatable {
    public static func ==(lhs: Self, rhs: Self) -> Bool
}

It really is that simple, all that’s required is a method, by which the operator receives two instances of the same type (in this example left-hand side [lhs] and right-hand side [rhs]), compares whether they are equal and provides a response.

How would we implement Swift Equatable?

The best way to demonstrate is with an example and here we have a very simple data Struct, which in this case is a User with a username a number of followers and a creation date. For the purposes of our example, usernames are unique and cannot be repeated.

Here’s our User:

struct User {
var username: String
var followers: Int
var createdAt: Date
}

We can now compare two users:

let userA = User(username: "qwerty", followers: 0, createdAt: Date())
let userB = User(username: "qwerty", followers: 0, createdAt: Date())

print(userA == userB)

This results in a compiler error: Binary operator ‘==’ cannot be applied to two ‘User’ operands.

To resolve this, let’s add an Equatable Extension * so we can can run a comparison to check if they’re the same:

extension User: Equatable {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.username == rhs.username
    }
}

It’s as easy as that and now we’ve implemented Equatable we can compare users for equality.

*Learn more about extensions in our Exploring Swift Extensions article.

Alright, time for our next protocol…

Swift Comparable

While Equatable allows us to compare whether elements are the same, Comparable takes this a step further by providing a way for us to compare (or sort) types to determine their relative order e.g. which user has the most followers. This Comparable` protocol can be really helpful to sort objects in an Array.

Comparable follows the same logic as Equatable, but implementation is more complex as there are four methods rather than one.

So, what’s under the hood?

Let’s take a look at the formal declaration for Comparable in Swift:

public protocol Comparable : Equatable {
    public static func <(lhs: Self, rhs: Self) -> Bool
    public static func <=(lhs: Self, rhs: Self) -> Bool
    public static func >(lhs: Self, rhs: Self) -> Bool
    public static func >=(lhs: Self, rhs: Self) -> Bool
}

As we mentioned, there are four different methods we can implement in Comparable.

Another important thing to flag is, as it inherits from Equatable (as comparison operations always involve equality checks), anything that conforms to Comparable must also conform to Equatable.

For our demonstration, as we’ve already implemented Equatable we don’t need to do this again, we can just add the Comparable element.

How would we implement Swift Comparable?

To implement Comparable, our Extension will be as follows:

extension User: Comparable {
    static func <(lhs: Self, rhs: Self) -> Bool {
        return lhs.followers < rhs.followers
    }
    
    static func <=(lhs: Self, rhs: Self) -> Bool {
        return lhs.followers <= rhs.followers
    }
    
    static func >(lhs: Self, rhs: Self) -> Bool {
        return lhs.followers > rhs.followers
    }
    
    static func >=(lhs: Self, rhs: Self) -> Bool {
        return lhs.followers >= rhs.followers
    }
}

Now we can perform a comparison between two users by the number of followers:

let userA = User(username: "qwerty", followers: 0)
let userB = User(username: "qwerty2", followers: 50)
let userC = User(username: "qwerty3", followers: 45)
let userD = User(username: "qwerty4", followers: 100)

print(userD > userA)
print(userA < userC)

Great! Now let’s take a look at Hashable

Swift Hashable

The Hashable protocol comes into its own when used in Collections. It provides a standard way of identifying types by a hash value, allowing them to be used in hash-based collections such as Dictionaries and Sets.

So, what’s under the hood?

Below is the formal declaration of the Hashable protocol in Swift:

public protocol Hashable : Equatable {
    func hash(into hasher: inout Hasher)
}

Hashable requires the implementation of a hash method, that will combine any of our properties to create a unique identifier.

Like Comparable, it requires whatever is hashed to conform to Equatable, as we’ll be comparing two different hashes.

How would we implement Swift Hashable?

As we only have two properties on our User, to implement the hasher we’ll need to combine both in our Extension, like this:

extension User: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(username)
        hasher.combine(followers)
    }
}

We can now place our User anywhere a Hashable would be needed, for example in a Dictionary.

As shown below, a Dictionary with a user as the key is now possible:

var dict = [User: String]()

dict[userA] = "randomString"

If we tried creating the Dictionary before implementing Hashable, we’d have seen the compiler error: Generic struct ‘Dictionary’ requires that ‘User’ conform to ‘Hashable’.

Last but not least, let’s have a look at Identifiable

Swift Identifiable

The Identifiable protocol builds upon Hashable and helps us easily identify any given type. It’s mostly used in Collections such as Lists and Grids and identifies instances using an ‘id’.

So, what’s under the hood?

In Swift, the declaration of the Identifiable protocol looks like this:

public protocol Identifiable {
    associatedtype ID: Hashable
    var id: ID { get }
}

We just add a hashable ‘id’ to any of our types – pretty straightforward.

How would we implement Swift Identifiable?

The simplest and most common approach to implementing Identifiable is by using a UUID, like this:

struct User: Identifiable {
    let id = UUID()
    var username: String
    var followers: Int
}

Now any User will have its own unique ‘id’.

But what if (like in our example) usernames are unique and we want to use them to identify a User?

To do this we could use a Computed Property (n this case the username) which can live in its own Extension, just like all other implementations.

To do this we’ll need to setup our User as it was before:

 struct User {
    var username: String
    var followers: Int
}

Then create the Extension that will be the Identifiable implementation:

extension User: Identifiable {
    var id: String {
        username
    }
}

Now we have a User that is Identifiable, we can use it anywhere this conformance is needed, such as in Swift Lists:

import SwiftUI

struct UserList: View {
    let users: [User]
    
    var body: some View {
        List(users) { user in
            Text(user.followers.description)
        }
    }
}

Creating this List without Identifiable conformance would return the compiler error: Initializer ‘init(_:rowContent:)’ requires that ‘User’ conform to ‘Identifiable’.

Swift Protocol Conformance FAQ

What are Swift Standard Library Protocols?

Swift Standard Library Protocols are rules or standards that parts of a program must follow to work well together in Swift, a programming language.

Why are protocols important in Swift programming?

Swift Standard Library Protocols are sets of rules that define common behaviors and functionalities for types and collections in Swift. These protocols allow different types to interact with each other effectively.

What is protocol conformance in Swift and why is it important?

Protocol conformance in Swift ensures that a type meets the requirements of a protocol, allowing it to be used consistently within the language’s type system. This is essential for maintaining robust and reliable code, as it ensures that types adhere to specified behaviors and can interact with other parts of a Swift program effectively.

Why are Equatable and Comparable protocols important in Swift?

Equatable and Comparable protocols are crucial in Swift for comparing types. Equatable allows the comparison of instances for equality, while Comparable enables sorting of instances based on their relative order.

Can you give examples of commonly used protocols in Swift?

Yes, some frequently used protocols in Swift are Equatable, Comparable, and Hashable. These help to sort objects, compare them, or use them as keys in dictionaries.

How do protocols improve software design in Swift?

Protocols help in organizing code better, making it easier to manage and change when needed. They ensure different parts of the program can work together smoothly.

What is protocol inheritance and how is it used in Swift?

Protocol inheritance lets one protocol take on the methods or properties of another. This means you can build more complex rules and behaviors from simpler ones.

Can a type conform to multiple protocols in Swift?

Yes, a type in Swift can conform to multiple protocols, allowing it to inherit behaviors and requirements from each protocol it adopts. This flexibility enables developers to design types that fulfill various criteria and support diverse functionalities within their codebase.

To sum up

In this article we’ve looked at four of the most common (and most useful), Swift Standard Library Protocols, they are:

  • Equatable: Used for direct comparison to understand if any two types are the same
  • Comparable: Used mostly for arithmetic comparisons to rank and sort types, using operations like < and >
  • Hashable: Needed for Dictionaries and Sets as it defines a way to uniquely identify a given element
  • Identifiable – Built upon Hashable, mostly used in Lists and Grids, and uses and ‘id’ to identify types

Hopefully this article has helped with understanding these protocols, and given you confidence to use them in your own apps and custom implementations.

Expect the Unexpected! Debug Faster with Bugfender
START FOR FREE

Trusted By

/assets/images/svg/customers/highprofile/rakuten.svg/assets/images/svg/customers/highprofile/schneider_electric.svg/assets/images/svg/customers/projects/slack.svg/assets/images/svg/customers/projects/ultrahuman.svg/assets/images/svg/customers/highprofile/macys.svg/assets/images/svg/customers/highprofile/axa.svg/assets/images/svg/customers/cool/domestika.svg/assets/images/svg/customers/cool/continental.svg

Already Trusted by Thousands

Bugfender is the best remote logger for mobile and web apps.

Get Started for Free, No Credit Card Required