Introduction to Protocol-Oriented Programming in Swift
When I started coding years ago, it was all about object oriented programming. With Swift, a new approach came up, making the code even easier to reuse and to test, Protocol-Oriented Programming.
Introducing the problem before introducing the solution, let’s assume we have this Vehicle class, and Car inheriting from it, using its speed value.
class Vehicle {
var speed : Double = 0.0
}
class Car : Vehicle {
override init() {
super.init()
self.speed = 90.0
}
}
But then, before cars, we used other transports like horse, which also have a specific speed. How can this new object behave as the others without inheritance? That’s where Protocol-Oriented Programming shines.
Protocol-Oriented Programming’s main benefits rely on making unrelated objets conform to the same behaviour. It can also be applied to different type of objects, like class, enum or struct, when OOP is limited to class only
Back to our transporter example, we could define a Transporter
protocol, defining the interface for our speed. I’m also going to switch to struct since we won’t use class inheritance anymore.
protocol Transporter {
var speed : Double { get }
}
struct Car : Transporter {
var speed: Double = 80
}
struct Horse : Transporter {
var speed: Double = 40
}
let car = Car()
let horse = Horse()
let transporters : [Transporter] = [car, horse]
transporters.forEach({ print("\($0.speed)") })
Now, a horse is an animal and need some rest when a car mainly need gas to keep going, it’s something we could also describe with more protocols since are not limited to the number of protocols we can implement.
protocol Motorised {
var tankCapacity : Int { get }
}
struct Car : Transporter, Motorised {
var speed: Double = 80
var tankCapacity: Int = 60
}
protocol Animal {
var restTime : Int { get }
}
struct Horse : Transporter, Animal {
var speed: Double = 40
var restTime: Int = 2
}
That is the best part, tomorrow if we have a new motorised animal, we could easily implement an object to represent it.
Another great point to Protocol-Oriented Programming is the ability to implement a default behaviour by extending protocol methods. How to know if one of my animal can transport by default?
protocol Animal {
var restTime : Int { get }
var canTransport : Bool { get }
}
extension Animal {
var canTransport : Bool { return self is Transporter }
}
Nice example but what about in a real iOS app?
Fair enough, a concrete example would be when you parse JSON objects from a network request. Not all your objects are parsable by default, only the one you would need. That’s where Protocol-Oriented Programming would be great again.
protocol LoggerProtocol {
func log(event: Events, parameters: [String: Any]?)
}
class LoggerManager {
private var loggers : [LoggerProtocol] = []
func log(event: Events, parameters: [String : Any]? = nil) {
self.loggers.forEach({ $0.log(event: event, parameters: parameters) })
}
}
Almost done. We have a logger helper class based on protocol to do all our logic. Easy to reuse and to test.
The advantage of protocols oriented programming is to make them easier to test. You don’t need to request your real server for your unit test, a mockup service implementing the right protocol would make the same result. If you want to learn more about this part, I recently describe an approach to write unit test in Swift following Protocol-Oriented Programming.
In conclusion, we’ve seen that Protocol-Oriented Programming in Swift is a great design pattern to conform unrelated objects to a same behaviour, either it’s a class, an enum or a struct. We’ve also discovered how to implement a default implementation, but also how it makes our code easier to reuse and test.