Updated for Xcode 14.2
Protocol extensions are a power feature in Swift, and you’ve already learned enough to be able to use them in your own apps. However, if you were curious and have some time to spare, we could go off on a little tangent and explore them further.
Note: This is definitely edging beyond beginner-level Swift, so please don’t feel under any pressure to continue on – you’re welcome to skip to the next chapter at any point!
Still here? Okay, let’s try another protocol extension, this time a little more complicated. First, we’ll write it as a simple extension on Int
:
extension Int {
func squared() -> Int {
self * self
}
}
let wholeNumber = 5
print(wholeNumber.squared())
That adds a squared()
method, which multiplies the number by itself to get the square so that our above code will print 25.
If we wanted that same method on a Double
without just copying the code across, we could follow the same pattern we did with Collection
: find a protocol that both Int
and Double
adopt, and extend that.
Here both types share the Numeric
protocol, because they are both numbers, so we could try to extend that:
extension Numeric {
func squared() -> Int {
self * self
}
}
However, that code won’t work any more, because self * self
can now be any kind of numbers, including Double
, and if you multiply a Double
by a Double
you definitely don’t get an Int
back.
To solve this we can just use the Self
keyword – I introduced that briefly when we looked at referring to static properties and methods, because it lets us refer to the current data type. It’s useful here because it means “whatever conforming type this method was actually called on”, and looks like this:
extension Numeric {
func squared() -> Self {
self * self
}
}
Remember, self
and Self
mean different things: self
refers to the current value, and Self
refers to the current type. So, if we had an integer set to 5, our squared()
function would effectively work like this:
func squared() -> Int {
5 * 5
}
There Self
is effectively Int
, and self
is effectively 5. Or if we had a decimal set to 3.141, squared()
would work like this:
func squared() -> Double {
3.141 * 3.141
}
If you’re still here, I think it’s safe to say you want to keep exploring protocol extensions even more, and who am I to disappoint? This is definitely just for very curious folks, though – you really do not need to know the following in order to start building apps with SwiftUI.
Still here? Okay, let’s start with a built-in protocol called Equatable
, which is what Swift uses to compare two objects using ==
and !=
.
We can make our User
struct conform to Equatable
like this:
struct User: Equatable {
let name: String
}
And now we can compare two users:
let user1 = User(name: "Link")
let user2 = User(name: "Zelda")
print(user1 == user2)
print(user1 != user2)
We don’t need to do any special work here, because Swift can make the Equatable
conformance for us – it will compare all the properties of one object against the same properties in the other object.
We can go further: there’s a Swift protocol called Comparable
, which allows Swift to see if one object should be sorted before another. Swift can’t automatically implement this in our custom types, but it’s not hard: you need to write a function called <
that accepts two instances of your struct as its parameter, and returns true if the first instance should be sorted before the second.
So, we could write this:
struct User: Equatable, Comparable {
let name: String
}
func <(lhs: User, rhs: User) -> Bool {
lhs.name < rhs.name
}
Tip: As you learn more about Swift, you’ll learn that the <
function can be implemented as a static function that helps keep your code a little more organized.
Our code is enough to let us create two User
instances and compare them using <
, like this:
let user1 = User(name: "Taylor")
let user2 = User(name: "Adele")
print(user1 < user2)
That’s neat, but the really clever thing is that Swift uses protocol extensions to make the following work too:
print(user1 <= user2)
print(user1 > user2)
print(user1 >= user2)
That code is possible because Equatable
lets us know whether user1
is equal to user2
, and Comparable
lets us know whether user1
should be sorted before user2
, and with those two pieces Swift can figure out the rest automatically.
Even better, we don’t even need to add Equatable
to our struct in order to get ==
to work. This alone is fine:
struct User: Comparable {
let name: String
}
Behind the scenes, Swift uses protocol inheritance so that Comparable
automatically also means Equatable
. This works similarly to class inheritance, so when Comparable
inherits from Equatable
it also inherits all its requirements.
Anyway, this has been a massive tangent – don’t worry if it left your head spinning a little, it’s just for curiosity and it will make more sense when you’re further on with your Swift journey!
SPONSORED From March 20th to 26th, you can join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer!
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.