Object oriented programming (OOP) is one of the most widely used programming paradigms today and it’s hard to imagine the majority of large software projects without it. While OOP is popular, drawbacks such as complicated inheritance and passing around objects to functions by reference (instead of value) lead to an unsafe and hard to maintain code base.
Swift tries to address these OOP drawbacks by introducing a new programming paradigm called Protocol Oriented Programming (POP). I highly recommend watching the WWDC 2015 talk on Protocol Oriented Programming if you haven’t already done so:
[embedyt] https://www.youtube.com/watch?v=g2LwFZatfTI[/embedyt]
So what are the advantages?
In POP, you can use protocols with structs, enums and classes, make types conform to multiple protocols and provide default implementations for protocols much like how you would if you were creating a base class for inheritance (but better since you aren’t restricted to class types). This makes it easier to encapsulate functional concepts without having to create an inheritance hierarchy. Let me show you what I mean:
I am going to define a protocol called Animal
that has name
and canSwim
properties and another protocol called Swimmable
which defines a speed
property.
protocol Animal {
var name: String { get }
var canSwim: Bool
}
protocol Swimmable {
var speed: Double { get }
}
In OOP, if you wanted to have an animal who could swim, you would have probably defined Swimmable
as the base class and then would have the Animal
class inherit from Swimmable
. This would lead to a not so ideal situation where you cannot separate Swimmable
from Animal
.
With POP, I could have an animal Dog
that does swim and can have a speed:
struct Dog: Animal, Swimmable {
var name: String
var canSwim: Bool
var speed: Double {
return 10.0
}
}
or an animal Cat
that doesn’t swim and so there’s no need for it to have a speed:
struct Cat: Animal {
var name: String
var canSwim: Bool
}
In inheritance, you would still have a speed for the Cat
.
You can also provide a default implementation to Animal
so that the canSwim
boolean is automatically calculated. I could now extend the Animal
struct and calculate the canSwim
boolean like this:
extension Animal {
var canSwim: Bool {
return self is Swimmable
}
}
Practical usage
Here at Rover, our design team has come up with a palette of colors, font weights, font sizes, borders, etc that are reused across the app. As you may know, UIKit doesn’t have the most robust styling support which makes it a pain to reuse styling code. To address this issue, we ended up creating something called RoverCustomViews
which uses protocols to bootstrap our own styling system.
First, we created a protocol called BackgroundColored
. This protocol has a roverBackgroundColor
method that returns a color:
extension Animal {
protocol BackgroundColored {
func roverBackgroundColor() -> UIColor
}
We then created a few specific color protocols that conform to the BackgroundColored
protocol and provided default implementations for each:
protocol Color1BackgroundColored: BackgroundColored {}
protocol Color2BackgroundColored: BackgroundColored {}
protocol Color3BackgroundColored: BackgroundColored {}
extension Color1BackgroundColored {
func roverBackgroundColor() -> UIColor {
return UIColor.white
}
}
extension Color2BackgroundColored {
func roverBackgroundColor() -> UIColor {
return UIColor.red
}
}
extension Color3BackgroundColored {
func roverBackgroundColor() -> UIColor {
return UIColor.blue
}
}
At this point, we created a class RoverCustomView
which checks to see if it conforms to the BackgroundColored protocol and if so, applies the correct color from the default implementation:
@objc open class RoverCustomView: UIView {
convenience init() {
self.init(frame: CGRect.zero)
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override open func awakeFromNib() {
super.awakeFromNib()
setupView()
}
fileprivate func setupView() {
if let bg = self as? BackgroundColored {
self.backgroundColor = bg.roverBackgroundColor()
}
}
}
We then created subclasses for RoverCustomView
that conforms to the correct BackgroundColored
protocol:
class RoverColor1View: RoverCustomView, Color1BackgroundColored {} //returns a white background view.
class RoverColor2View: RoverCustomView, Color2BackgroundColored {} //Returns a red background view.
class RoverColor3View: RoverCustomView, Color3BackgroundColored {} //Returns a blue background view.
With this in place, every time we need to use a view with a specific background color (in interface builder or in code), we can simply specify the correct subclass of RoverCustomView
to get the correct view. You can, of course, customize this further by adding other protocols such as DropShadowed
or CustomCornerRadius
. It’s easy to see how you can also apply this paradigm to other UIView
subclasses for customizations. The possibilities are endless.
Have any other insights on how to use protocols better? We’d love to hear them… or better yet come work with us, we’re hiring!