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.
Table of Contents
- Swift Equatable
- Swift Comparable
- Swift Hashable
- Swift Identifiable
- Swift Protocol Conformance FAQ
- What are Swift Standard Library Protocols?
- Why are protocols important in Swift programming?
- What is protocol conformance in Swift and why is it important?
- Why are Equatable and Comparable protocols important in Swift?
- Can you give examples of commonly used protocols in Swift?
- How do protocols improve software design in Swift?
- What is protocol inheritance and how is it used in Swift?
- Can a type conform to multiple protocols in Swift?
- To sum up
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 Compara
ble` 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 sameComparable
: Used mostly for arithmetic comparisons to rank and sort types, using operations like<
and>
Hashable
: Needed forDictionaries
andSets
as it defines a way to uniquely identify a given elementIdentifiable
– Built uponHashable
, mostly used inLists
andGrids
, 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.