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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Protocol-Oriented Programming Tutorial in Swift 5.1: Getting Started
25 mins
- Getting Started
- Why Protocol-Oriented Programming?
- Hatching the Egg
- Defining Protocol-Conforming Types
- Extending Protocols With Default Implementations
- Enums Can Play, Too
- Overriding Default Behavior
- Extending Protocols
- Effects on the Swift Standard Library
- Off to the Races
- Bringing It All Together
- Top Speed
- Making It More Generic
- Making It More Swifty
- Protocol Comparators
- Mutating Functions
- Where to Go From Here?
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!
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!
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.
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.