Back to blog

Protocols in Swift Explained


Aasif Khan
By Aasif Khan | Last Updated on January 3rd, 2022 6:41 am | 4-min read

Working with protocols is one of Swift’s most fundamental features. With protocols you define “rules” that an adopting class must conform to. This principle lets you write decoupled, modular and extensible Swift code.

In this tutorial you’ll learn how to work with protocols, and why they are useful for practical iOS development.

We’ll touch on Swift principles that rely on protocols to function, such as dependency injection and delegation. And of course, this tutorial has plenty of code that you can practice with.

What is a Protocol in Swift?

Let’s take a look at a simple protocol in Swift.

protocol Edible
{
func eat()
}

The protocol’s name is Edible, and we’ll use it for things that can be eaten, like food.

Similarly to how classes work, you define a protocol with the this syntax:

protocol name {
body
}

The Edible protocol defines one function called eat(). See how the protocol defines that function, but does not create an implementation for it?

Compare that to a class, for example, and you’ll see the biggest difference between classes and protocols. Protocols merely define functions and properties, and don’t implement them.

Let’s look at the official definition of a protocol:

A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality.

You could say that a protocol defines rules or “requirements”, such as functions and properties. Other classes can then adopt those rules, and provide an actual implementation. Any class that satisfies the rules of a protocol, is said to conform to that protocol.

Let’s look at how a class could adopt the Edible protocol. Like this:

class Apple: Edible
{
func eat()
{
print(“Omnomnom! Eating the apple…”)
}
}

A few things stand out here:

  • The Apple class adopts the Edible protocol by writing it after the class name, separated by a colon. This is similar to subclassing syntax.
  • The Apple class then conforms to the Edible protocol by implementing the eat() function. It repeats the function declaration func eat() { and provides a function body.

Easy-peasy! We’ve defined a protocol Edible and implemented it in the class Apple.

You could say that the apple can be eaten, because it has that function eat(). But instead of just hoping that Apple has a function eat(), we’ve formalized that connection with the structure of our code.

Why Protocols Are Useful

But… why use protocols at all? Couldn’t we just have given the Apple class a function eat()? Why do you need a protocol to define it?

The power of protocols is that they formalize the connection between different parts of your code, without providing implementations. This allows you to build rigid structures in your code, without tightly coupling your code’s components.

Let’s find out what that exactly means… We’re going to start by creating a simple class, like this:

class Person
{
var name:String = “”

func provideSnack(withItem item: Apple)
{
item.eat()
}
}

In the above code sample you’ve created a class Person. It has one property name of type String. And it has one function provideSnack(withItem:).

The provideSnack(withItem:) function has a parameter item of type Apple. When this function is called, it subsequently calls eat() on the Apple instance and supposedly “eats” the apple as a snack.

Let’s put those Person and Apple classes to use. Like this:

let apple = Apple()

let bob = Person()
bob.name = “Bob”

bob.provideSnack(withItem: apple)

// Output: Omnomnom! Eating the apple…

Note: In the function provideSnack(withItem:), the withItem part is a so-called argument label. Within the function you can use the parameter name item, and outside of the function you use the argument label withItem. This makes the function more descriptive.

So, what’s the problem with the Person class? It can only eat apples! And even worse: the Person and Apple class are now tightly-coupled, because they rely on each other’s implementation to function properly. Is there a way to overcome that disadvantage? That’s where protocols come in.

First, we’re going to change the provideSnack(withItem:) function. Like this:

func provideSnack(withItem item: Edible)
{
item.eat()
}

The type of the item parameter is Edible now, instead of Apple. So, we’ve indicated that item can be any type as long as it conforms to Edible.

Let’s test that. Create another food class, like this:

class CandyBar: Edible
{
func eat()
{
print(“*** munch, munch *** Mmmm, tasty candy bar!”)
}
}

See how this CandyBar class also adopts and conforms to Edible? You can easily provide an instance of that class to the provideSnack(withItem:) function. Like this:

let candy = CandyBar()
bob.provideSnack(withItem: candy)
// Output: *** munch, munch *** Mmmm, tasty candy bar!

So, what’s really happening here?

  • Instead of coupling the Person and Apple classes with each other, we’ve simply added the Edible protocol as a requirement for the provideSnack(withItem:) function.
  • Essentially, the provideSnack(withItem:) function doesn’t care what item you provide, as long as it conforms to Edible.
  • Because the protocol defines a function eat(), you can call that function on item without needing to know its exact type.

You can summarize that as follows… The Person class can use any class that conforms to Edible, without knowing the exact implementation of that class. This increases the flexibility and composability of your code, and makes it more loosely coupled.

That’s the power of protocols!

Protocols as Types

Protocols are fully-fledged types in Swift. That means you can use protocols in many places where other types are allowed. Let’s look at an example:

func provideSnacks(items: [Edible])
{
for item in items {
item.eat()
}
}

See how the type of the items array in the above function is [Edible], or array-of-Edible?

You can put any type in an array, such as Int or String. And because a protocol is a type too, you can also use a protocol as the type of the array. That makes your code even more flexible, without losing the ability to formalize the structure of your code.

Because protocols are types, you can use them in many places, including:

  • As a parameter type or return type in a function
  • As the type of a constant, variable, or property
  • As the type of items in an array, dictionary, or other collections

You can even combine protocols and generics by using associatedtype.

Protocols in Practical iOS Development

Protocols have another advantage. Let’s say that you and I are working on the same app. It’s a restaurant app, and I’m building the kitchen and you’re building the tables, waiters and guests.

At one point I need to hand-off a pizza, that’s made in the kitchen, to the waiter. Because you’re building the waiter, you need to know from me what kind of foods I’m going to provide to the waiter.

And pizza’s aren’t the only thing the kitchen can provide. It cooks steak, serves beer, makes soups, and so on.

So, we come up with an elaborate structure that the waiter can work with. We use subclassing and inheritance, but then we discover that a steak and a beer can’t really inherit from the same superclass. They are too different!

And we also want to keep the waiters lean, and don’t let them rely too much on the inner workings of the kitchen. We realize that the only information a waiter really needs, is whether an item that comes from the kitchen can be carried from the kitchen to the table.

So, we make a Servable protocol:

protocol Servable
{
func pickup()
func carry()
func serve()
}

As a result, the waiters now know that anything that comes from the kitchen can be served. They’ve defined so in their own code that anything the kitchen provides, needs to conform to the Servable protocol.

You and I, we can work on our own implementations of the kitchen and the restaurant. The only rule we’ve formalized is how the kitchen communicates with the waiters. And that’s all they need to know!

When the kitchen changes the way beer is served, the waiters can still serve that beer to a guest’s table. And when a new waiter is hired, they only need to get trained in calling the functions of Servable regardless of their implementation.

This is of course just a story. It helps you understand the moving parts in an iOS app by exaggerating them. In practical iOS development you’re probably not going to code a restaurant with waiters. So, what are you going to use protocols for?

Let’s take a look at a few examples:

  • Delegation is one of the most common uses for protocols. Delegation works by off-handing functionality from a base class to another delegate class. This base class is commonly outside the control of a developer, but by using a delegate you can still affect its functionality. And you’ve guessed it, delegation uses protocols to define what functionality can be handed off. Delegation is used prominently in Apple platforms and SDKs, so it’s a must to master. Common use cases include table views, CLLocationManager and passing data between view controllers.
  • Dependency Injection is a technique to make your code’s components easier to test. It uses protocols to create shallow implementations of certain types, called stubs, and fake implementations of certain objects, called mocks. Imagine you’re building a car with an engine. You want to test the car and the engine separately. So, you create a protocol that defines the interaction between the car and the engine. You inject a “fake” engine into the car to test how the car responds to an engine, and you mock a fake car with a live engine to test how the engine responds to a car.
  • Protocol-Oriented Programming (POP) is a hodgepodge of principles, concepts and best practices that give composable protocols a more prominent role in practical iOS development. You could see Protocol-Oriented Programming as an extension of Object-Oriented Programming. With POP you create functionality in your app by composing different protocols, instead of defining a rigid and hierarchical class structure by using inheritance. Protocol-Oriented Programming increases the modularity of your code by making individual components leaner, and at the same time it gives more control over the exact implementation of functionality. Opinions and practical applications of POP differ greatly. A good starting point is this WWDC video from 2015.

Further Reading

So, now you know! With a protocol you can define rules that an adopting class needs to conform to. And that has all sorts of benefits, such as letting one class work with another without knowing its exact implementation.


Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts