Updated for Xcode 14.2
Protocols let us define contracts that conforming types must adhere to, and extensions let us add functionality to existing types. But what would happen if we could write extensions on protocols?
Well, wonder no more because Swift supports exactly this using the aptly named protocol extensions: we can extend a whole protocol to add method implementations, meaning that any types conforming to that protocol get those methods.
Let’s start with a trivial example. It’s very common to write a condition checking whether an array has any values in, like this:
let guests = ["Mario", "Luigi", "Peach"]
if guests.isEmpty == false {
print("Guest count: \(guests.count)")
}
Some people prefer to use the Boolean !
operator, like this:
if !guests.isEmpty {
print("Guest count: \(guests.count)")
}
I’m not really a big fan of either of those approaches, because they just don’t read naturally to me “if not some array is empty”?
We can fix this with a really simple extension for Array
, like this:
extension Array {
var isNotEmpty: Bool {
isEmpty == false
}
}
Tip: Xcode’s playgrounds run their code from top to bottom, so make sure you put that extension before where it’s used.
Now we can write code that I think is easier to understand:
if guests.isNotEmpty {
print("Guest count: \(guests.count)")
}
But we can do better. You see, we just added isNotEmpty
to arrays, but what about sets and dictionaries? Sure, we could repeat ourself and copy the code into extensions for those, but there’s a better solution: Array
, Set
, and Dictionary
all conform to a built-in protocol called Collection
, through which they get functionality such as contains()
, sorted()
, reversed()
, and more.
This is important, because Collection
is also what requires the isEmpty
property to exist. So, if we write an extension on Collection
, we can still access isEmpty
because it’s required. This means we can change Array
to Collection
in our code to get this:
extension Collection {
var isNotEmpty: Bool {
isEmpty == false
}
}
With that one word change in place, we can now use isNotEmpty
on arrays, sets, and dictionaries, as well as any other types that conform to Collection
. Believe it or not, that tiny extension exists in thousands of Swift projects because so many other people find it easier to read.
More importantly, by extending the protocol we’re adding functionality that would otherwise need to be done inside individual structs. This is really powerful, and leads to a technique Apple calls protocol-oriented programming – we can list some required methods in a protocol, then add default implementations of those inside a protocol extension. All conforming types then get to use those default implementations, or provide their own as needed.
For example, if we had a protocol like this one:
protocol Person {
var name: String { get }
func sayHello()
}
That means all conforming types must add a sayHello()
method, but we can also add a default implementation of that as an extension like this:
extension Person {
func sayHello() {
print("Hi, I'm \(name)")
}
}
And now conforming types can add their own sayHello()
method if they want, but they don’t need to – they can always rely on the one provided inside our protocol extension.
So, we could create an employee without the sayHello()
method:
struct Employee: Person {
let name: String
}
But because it conforms to Person
, we could use the default implementation we provided in our extension:
let taylor = Employee(name: "Taylor Swift")
taylor.sayHello()
Swift uses protocol extensions a lot, but honestly you don’t need to understand them in great detail just yet – you can build fantastic apps without ever using a protocol extension. At this point you know they exist and that’s enough!
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.