Skip to main content

What do the Comparable, Equatable, and Hashable protocols do?

· 4 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Equatable Implementing this protocol allows one instance of an object to be compared for equality with another instance of an object of the same type. You’ve probably leveraged this protocol without realizing it as theEquatableprotocol is what allowsus to use == to check the equality of two objects.

Adding Equatable support to your object allows your object to gain access to many convenient Swift APIs automatically. Furthermore, since Equatable is the base protocol for Hashable and Comparable, by adding Equatable conformance,we can easily extend our implementation to support creating Sets, sorting elements of a collection, and much more.

Let’s take a look at our Money struct:

struct Money {
let value: Int
let currencyCode: String
}

Currently, there’s no easy way for us to check if one Money object is equal to another Money object.

We can change that by conforming to Equatable:

struct Money: Equatable {
let value: Int
let currencyCode: String

static func == (lhs: Money, rhs: Money) -> Bool {
lhs.currencyCode == rhs.currencyCode && lhs.value == rhs.value
}
}

Now, our adherence to the Equatable protocol and our subsequent custom implementation of the == operator allows us to easily compare any two Money objects for equality.

Technically, we did some extra work though...

Since Int and String types are Equatable themselves, Swift can automatically synthesize the == implementation for us.

So, it’d be sufficient to just write:

struct Money: Equatable {
let value: Int
let currencyCode: String
}

Hashable When an object implements the Hashable protocol it introduces a hashValue property which is useful in determining the equality of two objects and allows that object to be used with a Set or Dictionary.

The hashValue is an Integer representation of the object that will always be the same for any two instances that compare equally. In simple terms, this means that if we have two instances of an object A and B then if A == B it must follow that A.hashValue == B.hashValue.

However, the reverse isn’t necessarily true. Two instances can have the same hashValue, but may not necessarily equal one another. This is because the process that creates the hashValue can occasionally create situations where two different instances generate the same hashValue.

The Hashable protocol only guarantees that two instancesthat are already known to be equal will also have the same hashValue.

It’s easy to add support for the Hashable protocol, but note that Hashable requires conformance to the Equatable protocol as well.

From Swift 4.1 onwards, we can have the compiler automatically synthesize conformance for us just like we did with Equatable. This functionality is only an option when the properties within the object conform to Hashable.

struct Money: Hashable {
let value: Int
let currencyCode: String
}

Otherwise, we’ll have to implement the hashValue generation ourselves. Swift 4.2 introduced a Hasher type that provides a randomly seeded universal hash function for us to use in our custom implementations:

struct Money: Hashable {
let value: Int
let currencyCode: String

func hash(into hasher:inout Hasher) {
hasher.combine(value)
hasher.combine(currencyCode)
}
}

Conforming to the Hashable protocol allows you to use this object in a Set or as a Dictionary key. Many types in the standard library conform to Hashable: Strings, Integers, Floats, Booleans, and even Set are hashable by default.

Comparable The Comparable protocol allows us to use our customtype with the <, <=, >=, and > operators.

The implementation of this protocol is quite clever. We only have to implement the less than operator < since the implementations of all ofthe other comparison operators can be inferred from < and our Equatable conformance.

This conformance allows us to use handy Swift methods like sorted(),min(), and max()on our objects in collections.

struct Money: Comparable {
let value: Int
let currencyCode: String

static func < (lhs: Money, rhs: Money) -> Bool {
lhs.value < rhs.value
}
}