UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to get the most from protocol extensions

Paul Hudson    @twostraws   

Updated for Xcode 15

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
}

Want to go further?

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!

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.9/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.