Protocol-Oriented Programming Tutorial in Swift 5.1: Getting Started

In this protocol-oriented programming tutorial, you’ll learn about extensions, default implementations and other techniques to add abstraction to your code. By Andy Pereira.

4.8 (53) · 3 Reviews

Download materials
Save for later
Share
Update note: Andy Pereira updated this tutorial for Swift 5.1 and Xcode 11. Erik Kerber wrote the original.

Protocols are a fundamental feature of Swift. They play a leading role in the structure of the Swift standard library and are a common method of abstraction. They provide a similar experience to interfaces that some other languages have.

This tutorial will introduce you to a software engineering practice called protocol-oriented programming, which has become somewhat a fundamental in Swift. It really is something you need to grasp if you’re learning Swift!

In this tutorial, you’ll learn about:

  • The difference between object-oriented and protocol-oriented programming.
  • Protocols with default implementations.
  • Extending the Swift standard library.
  • Further extending protocols with generics.

What are you waiting for? Time to start your Swift engines!

Note: This tutorial assumes you know the basics of Swift development and more advanced topics like generics. If you are new to Swift development, check out Swift Tutorial for Beginners and Swift Generics Tutorial: Getting Started beforehand.

Getting Started

Imagine you’re developing a racing video game. You want players to be able to drive cars, ride motorcycles and pilot planes. They can even ride different birds — because it’s a video game — and you can drive whatever you want! The key here is that there are lots of different “things” that can be driven or piloted.

A common approach for this type of app is object-oriented programming, where you can encapsulate all of the logic inside base classes that other classes then inherit from. The base classes would have the “drive” and “pilot” logic in them.

You start programming your game by creating classes for each vehicle. Put a pin in the bird concept for now. You’ll work on that later.

As you code, you notice that Car and Motorcycle share some functionality, so you create a base class called MotorVehicle and add them to it. Car and Motorcycle then inherit from MotorVehicle. You also design a base class called Aircraft that Plane inherits from.

You think, “This is going great.” But wait! Your racing game is set in the year 30XX, and some cars can fly.

Now, you face a predicament. Swift doesn’t support multiple inheritance. How can your flying cars inherit from both MotorVehicle and Aircraft? Do you create another base class that merges the two functionalities? Probably not, since there’s no clean and easy way to do this.

Who will save your racing game from this disastrous dilemma? Protocol-oriented programming to the rescue!

Our hero, protocol-oriented programming Swift, flying to the rescue

Why Protocol-Oriented Programming?

Protocols allow you to group similar methods, functions and properties. Swift lets you specify these interface guarantees on class, struct and enum types. Only class types can use base classes and inheritance.

An advantage of protocols in Swift is that objects can conform to multiple protocols.

When writing an app this way, your code becomes more modular. Think of protocols as building blocks of functionality. When you add new functionality by conforming an object to a protocol, you don’t build a whole new object. That’s time-consuming. Instead, you add different building blocks until your object is ready.

Converting base classes into protocols solves your video game dilemma. With protocols, you can create a FlyingCar class that conforms to both MotorVehicle and Aircraft. Neat, huh?

Time to get hands-on and take this racing concept for a spin.

Man practicing protocol-oriented programming at a table

Hatching the Egg

Begin by opening Xcode and then creating a new playground named SwiftProtocols.playground. Then add this code to it:

protocol Bird {
  var name: String { get }
  var canFly: Bool { get }
}

protocol Flyable {
  var airspeedVelocity: Double { get }
}

Build the playground with Command-Shift-Return to make sure that it compiles properly.

This code defines a simple protocol, Bird, with properties name and canFly. It then defines a protocol called Flyable, which has the property airspeedVelocity.

In the pre-protocol days of yore, developers would start with Flyable as a base class and then rely on object inheritance to define Bird and any other things that fly.

But in protocol-oriented programming, everything starts as a protocol. This technique allows you to encapsulate the functional concept without needing a base class.

As you’re about to see, this makes the entire system much more flexible when defining types.

Defining Protocol-Conforming Types

Start by adding the following struct definition to the bottom of the playground:

struct FlappyBird: Bird, Flyable {
  let name: String
  let flappyAmplitude: Double
  let flappyFrequency: Double
  let canFly = true

  var airspeedVelocity: Double {
    3 * flappyFrequency * flappyAmplitude
  }
}

This code defines a new struct named FlappyBird that conforms to both the Bird and Flyable protocols. Its airspeedVelocity is a computed property comprising of flappyFrequency and flappyAmplitude. Being flappy, it returns true for canFly.

Next, add the following two struct definitions to the bottom of the playground:

struct Penguin: Bird {
  let name: String
  let canFly = false
}

struct SwiftBird: Bird, Flyable {
  var name: String { "Swift \(version)" }
  let canFly = true
  let version: Double
  private var speedFactor = 1000.0
  
  init(version: Double) {
    self.version = version
  }

  // Swift is FASTER with each version!
  var airspeedVelocity: Double {
    version * speedFactor
  }
}

A Penguin is a Bird, but it cannot fly. Good thing you didn’t take the inheritance approach and make all birds Flyable!

Using protocols, you can define functional components and have any relevant object conform to them.

You then declare SwiftBird, but in our game there are different versions of SwiftBird. The higher the version property is, the faster its airspeedVelocity as defined by the computed property.

However, you can see there are redundancies. Every type of Bird has to declare whether it canFly or not — even though a notion of Flyable already exists in your system. It’s almost like you need a way to define default implementations of protocol methods. Well, that’s where protocol extensions come in.

Extending Protocols With Default Implementations

Protocol extensions allow you to define a protocol’s default behavior. To implement your first one, insert the following just below the Bird protocol definition:

extension Bird {
  // Flyable birds can fly!
  var canFly: Bool { self is Flyable }
}

This code defines an extension on Bird. It sets the default behavior for canFly to return true whenever the type conforms to the Flyable protocol. In other words, any Flyable bird no longer needs to explicitly declare it canFly. It will simply fly, as most birds do.

Now delete the let canFly = ... from FlappyBird, Penguin and SwiftBird. Build the playground again. You’ll notice that the playground still builds successfully because the protocol extension now handles that requirement.