“How can I use protocol-oriented programming in my everyday coding?” Natasha tackles this question and focuses on the practical applications for POP including Views, View Controllers, and Networking. Follow along with this talk from App Builders CH to shift your mindset from object-oriented to protocol-oriented when working in Swift to make your code cleaner and more readable!
Back to reality – We are all going to assume that Swift is the best programming language.
Today I am going to talk about protocol-orientated programming in Swift, focusing on the practical side. Let’s call it POP.
Protocol-Oriented Programming in Swift (00:37)
When Swift first came out, it was exciting to learn new things. For the first year, I was happy to learn it, and I was writing my objective C-code in Swift (sometimes using value types and more fancy things). But at WWDC last year, protocol extensions were introduced.
Dave Abrahams (Professor of Blowing-Your-Mind) delivered a mind-blowing talk called “Protocol-Oriented Programming in Swift”. He claimed that “Swift is a protocol-oriented programming language.” If you look into the Swift standard libraries, there are over 50 protocols. The way the language itself is made, it uses many protocols and that is something we should also be using. Dave also gave examples of how protocols can change your code. He used example of drawables. He took squares, triangles and circles and used protocols to make them more amazing. Watching it I was amazed, but it was hard for me to relate to, because I do not use drawables in my everyday code.
I went back and I tried thinking of examples of how I could use protocol-oriented programming in my day-to-day code. We have patterns that we are used to from Objective-C and other programming experiences, but it is hard to change your mindset from object-oriented to protocol-oriented.
Practical Pop! (03:05)
Over the past year, I have been experimenting on how to use protocols, and I want to share some examples that can make your code better. Since this is practical protocol-oriented programming, I am going to cover View
, (UITable)ViewController
, and Networking
. Hopefully this will help you consider places where you can use protocols too.
Views (03:24)
Let’s say your product manager comes over and says, “We want a view that when you click a button, it starts shaking.” This is a very common animation with password fields for example – when the user enters the wrong password, it might shake.
I usually start with Stack Overflow (laughs). Someone may even have the basic code in Swift for shaking objects, and I do not even have to think about it except to perhaps modify the shaking a little. The hard part, however, is the architecture: Where do I put this in my code?
// FoodImageView.swift
import UIKit
class FoodImageView: UIImageView {
func shake() {
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 4.0, self.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 4.0, self.center.y))
layer.addAnimation(animation, forKey: "position")
}
}
I am going to subclass my UIImageView
, create my FoodImageView
and add a shake animation:
// ViewController.swift
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var foodImageView: FoodImageView!
@IBAction func onShakeButtonTap(sender: AnyObject) {
foodImageView.shake()
}
}
In my view controller, from interface builder I connect my view, subclass it to FoodImageView
, I have the shake function, and done!. I did this whole task in 10 minutes. I am happy. My code works great.
Later, the product manager comes over and she says, “You have to shake the button when you shake the view.” So now I am going to go back and repeat the same thing for the button.
// ShakeableButton.swift
import UIKit
class ActionButton: UIButton {
func shake() {
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 4.0, self.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 4.0, self.center.y))
layer.addAnimation(animation, forKey: "position")
}
}
Subclass, create a button, add a shake()
function, and my ViewController
. Now I can shake the food image view and the button, and I am done.
// ViewController.swift
class ViewController: UIViewController {
@IBOutlet weak var foodImageView: FoodImageView!
@IBOutlet weak var actionButton: ActionButton!
@IBAction func onShakeButtonTap(sender: AnyObject) {
foodImageView.shake()
actionButton.shake()
}
}
Hopefully, this gives you a warning: I duplicated the shaking code in two places. If I need to change how far it shakes, I have to find it in two places, which is not great.
// UIViewExtension.swift
import UIKit
extension UIView {
func shake() {
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 4.0, self.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 4.0, self.center.y))
layer.addAnimation(animation, forKey: "position")
}
}
Being good programmers, we are going to immediately notice this and try to re-factor. If you are coming from Objective-C, I would create a UIView
category or, in Swift, it is an extension.
I can take that away, since UIButton
and UIImageView
are both UI views. I can extend the UI view and add the shake function there. Now I can still have other logic in my button and my image view, but that shake function is available everywhere.
class FoodImageView: UIImageView {
// other customization here
}
class ActionButton: UIButton {
// other customization here
}
class ViewController: UIViewController {
@IBOutlet weak var foodImageView: FoodImageView!
@IBOutlet weak var actionButton: ActionButton!
@IBAction func onShakeButtonTap(sender: AnyObject) {
foodImageView.shake()
actionButton.shake()
}
}
Right away we can see that it is less readable. For example, with the foodImageView
and the actionButton
, you do not see any intentions for it to shake. There is nothing in the whole class that tells you it should shake. It is not clear because there is a random shake function somewhere.
If you have done this much for categories and extending UI views, you might have had better experience. It becomes this Frankenstein garbage place where you start adding a shake and then someone comes and says, “We want a dimmable view”. Then you add a dim function and then there is some other random function. It becomes this long, unreadable, hard-to-find garbage file with all these random things that UI view could do, but only maybe one or two things need to do that.
It is not clear what the intention is. How can we change this?
This is a talk about protocol-oriented programming, so we are going to use protocols. Let’s create a Shakeable
protocol:
// Shakeable.swift
import UIKit
protocol Shakeable { }
extension Shakeable where Self: UIView {
func shake() {
// implementation code
}
}
With protocol extensions, you can constrain them to specific classes. In this case, I can take out my shake function, and, with the category, I can say that only things that conform to this protocol, only UI views, will have this function.
You still have the same powerful functionality of the extension that you originally wanted to, but you have this in protocols. Anything that is not a view that conforms to this protocol, will not work. It is only for views where the shake can be a default implementation.
class FoodImageView: UIImageView, Shakeable {
}
class ActionButton: UIButton, Shakeable {
}
We can see that the FoodImageView
and the ActionButton
conform to the Shakeable
protocol. They will have the shake function, but it is now more readable –- I can now understand that shaking is the intention. If I am using this view somewhere else, I have to think, “is this supposed to shake here also?”. It adds readability, but still the code is isolated and reusable.
Let’s say we want to shake and dim the view. We can have another protocol, a Dimmable
protocol that has a protocol extension for dimming, and we can add that in. Again, the intention is clear just by looking at the class definition of what we intend this view to do.
class FoodImageView: UIImageView, Shakeable, Dimmable {
}
For re-factoring, when they say “we do not want it to shake anymore”, you delete conformants to the Shakeable
protocol.
class FoodImageView: UIImageView, Dimmable {
}
Now it is only dimmable. It is very easy to plug and have that Lego-like architecture by using protocols. Check this article if you want to follow up on more powerful ways to do views with protocols, creating the dimmable view with transitions.
Now we are happy and we can go eat Pop-tarts.
(UITable)ViewControllers (10:09)
This is an app, Instagram for food: it shows you pictures of food from different places.
// FoodLaLaViewController
override func viewDidLoad() {
super.viewDidLoad()
let foodCellNib = UINib(NibName: "FoodTableViewCell", bundle: nil)
tableView.registerNib(foodCellNib,
forCellReuseIdentifier: "FoodTableViewCell")
}
This is a tableView
. There is basic code that we keep writing all the time. When the view loads, we want to load our cell from the Nib
; we specify our NibName
and then we register the Nib
with a ReuseIdentifier.
let foodCellNib = UINib(NibName: String(FoodTableViewCell), bundle: nil)
tableView.registerNib(foodCellNib,
forCellReuseIdentifier: String(FoodTableViewCell))
Unfortunately, due to the way UIKit is set up, we have to use strings. I like using the same identifiers for my cells as the cell name.
We can see immediately the inefficiency. If you are coming from Objective-C, I used to use NSString
from class. In Swift you can use String
(a bit better), and if this was Objective-C we would be done. We are used to doing this using String
, but if an intern comes to our code base and they have not done iOS, it is not the most intuitive method. You are randomly string-ifying some name, “Why are you doing that?”. Also, if you do not specify the identifier in the storyboard, now it is crashing and they have no idea why. How can we make this better?
protocol ReusableView: class {}
extension ReusableView where Self: UIView {
static var reuseIdentifier: String {
return String(self)
}
}
extension UITableViewCell: ReusableView { }
FoodTableViewCell.reuseIdentifier
// FoodTableViewCell
Because we are not in Objective-C anymore, we can use a reusable view protocol for the cells.
let foodCellNib = UINib(NibName: "FoodTableViewCell",
bundle: nil)
tableView.registerNib(foodCellNib,
forCellReuseIdentifier: FoodTableViewCell.reuseIdentifier)
protocol NibLoadableView: class { }
extension NibLoadableView where Self: UIView {
static var NibName: String {
return String(self)
}
}
Again, every single reuse identifier in our table view is going to be the string version of the class. We can use a protocol extension for every view. This could apply to UICollectionView
UITableView
, Cell
. This is our reuse identifier. We have isolated the annoying logic that we have to use because of UIKit
. Now we can extend every single UITableViewCell
.
extension FoodTableViewCell: NibLoadableView { }
FoodTableViewCell.NibName
// "FoodTableViewCell"
We can do the same for UICollectionViewCell
to extend this reusable view protocol. Every single cell has a default reuseIdentifier
that we never have to type again or worry about. We say, FoodTableViewCell
, reuseIdentifier
, and it is going to string-ify the class and do this for us.
This is still long, but it is more readable:
let foodCellNib = UINib(NibName: FoodTableViewCell.NibName, bundle: nil)
tableView.registerNib(foodCellNib,
forCellReuseIdentifier: FoodTableViewCell.reuseIdentifier)
extension UITableView {
func register<T: UITableViewCell where T: ReusableView, T: NibLoadableView>(_: T.Type) {
let Nib = UINib(NibName: T.NibName, bundle: nil)
registerNib(Nib, forCellReuseIdentifier: T.reuseIdentifier)
}
}
We can do the same for the NibName
, because we do not want to be dealing with strings. We can create a NibLoadableView
(any view that is going to load from the Nib
). We will have a NibName
, and it will return the string version of the class name.
let foodCellNib = UINib(NibName: "FoodTableViewCell", bundle: nil)
tableView.registerNib(foodCellNib,
forCellReuseIdentifier: "FoodTableViewCell")
Any view that does load from the Nib
, i.e. our TableViewCell
, is going to conform to the Nib loadable view protocol. It will automatically have a NibName
property that will string-ify the class name. At least an intern can understand that we are getting the cell’s NibName
, the cell’s reuseIdentifier
, across every single TableViewCell
, and every time we register the class.
We can go one step further and use generics to register our cells and extract these two lines of code.
tableView.register(FoodTableViewCell)
We can extend our tableView
and create a register class that takes in a type that conforms to these two protocols. It has a reuser identifier and a Nib
name from those protocols. Now we can completely extract that logic for those two lines of code where the Nib
name conforms to NibLoadableView
. We know it has a NibName
property, and that the cell will conform to the reusable view protocol (it is going to have the reusable identifier properties). Those two lines of code that we have to type in every single table view can now be extracted. Just one line of code, and we are registering the cell, which is much cleaner. You do not have to deal with strings.
We can take this a step further again. We have to register the cells, and we have to de-queue the cells. We can use these generics and protocols to extract that ugliness: when you de-queue, you have to say what is the reuseIdentifier
. In Swift, it is all these lines of code because we have optionals.
extension UITableView {
func dequeueReusableCell<T: UITableViewCell where T: ReusableView>(forIndexPath indexPath: NSIndexPath) -> T {
guard let cell = dequeueReusableCellWithIdentifier(T.reuseIdentifier, forIndexPath: indexPath) as? T else {
fatalError("Could not dequeue cell with identifier: \(T.reuseIdentifier)")
}
return cell
}
}
When you de-queue a cell, you have to have this guard statement that de-queues the cell; if it is not there, you have to either do a fatal error, or explicit unwrapping.
guard let cell = tableView.dequeueReusableCellWithIdentifier(“FoodTableViewCell", forIndexPath: indexPath)
as? FoodTableViewCell
else {
fatalError("Could not dequeue cell with identifier: FoodTableViewCell")
}
This line of code is always ugly when you type it in. It comes from Objective-C, we have it from UIKit, we do not have much control over it. But using the protocols we can extract this ugliness, because we have the reuseIdentifier
for every single table view cell.
let cell = tableView.dequeueReusableCell(forIndexPath: indexPath) as FoodTableViewCell
We can go from this (above) to this (below):
if indexPath.row == 0 {
return tableView.dequeueReusableCell(forIndexPath: indexPath) as DesertTableViewCell
}
return tableView.dequeueReusableCell(forIndexPath: indexPath) as FoodTableViewCell
Every time we are de-queuing a cell, we do it as we want to. We de-queue the cell for forIndexPath
and we say what the cell is. If you have multiple cells, you cast it to that cell that you registered and it will immediately know what type it is.
This is magical! A great way of taking the old that we have from Objective-C, now mixed with Swift and optionals, using protocols and generics, and having nicer code that we can use throughout our project.
iOS Cell Registration & Reusing with Swift Protocol Extensions and Generics (17:28)
This is taken from Guille Gonzalez, he applies this to collection view, but you can also apply this to other parts of UIKit that we have to struggle with, for example Protocol-Oriented Segue Identifiers in Swift. You can use protocols to get that away from your everyday code and be safer. This was also taken from Apple’s example at WWDC last year. Protocol-oriented programming is awesome.
Networking (18:25)
With networking you probably have to make API calls. This is what I usually do: I have some service (e.g. I am fetching food from the server), I have a get
function that will go to the API and get the result. I would like to use Swift’s error handling, but because it is asynchronous I cannot throw errors.
struct FoodService {
func get(completionHandler: Result<[Food]> -> Void) {
// make asynchronous API call
// and return appropriate result
}
}
I am going to use the resulting enum, which is a popular pattern that comes from Haskel, used in Swift.
The resulting enum is simple. When the server returns the result, we can parse it into either a success and return it is going to be an array of food items. If it is a failure, we can return an error and then the view controller from the completion handler will figure out what to do in those cases.
enum Result<T> {
case Success(T)
case Failure(ErrorType)
}
This is using our completion handler when the server gets the response asynchronously, we are going to pass in the result of food items.
struct FoodService {
func get(completionHandler: Result<[Food]> -> Void) {
// make asynchronous API call
// and return appropriate result
}
}
Now the view controller is going to parse the items. We will have a dataSource
in our view controller, which is an array of food. When the view loads, we will make that asynchronous API call, and get the result in our completion handler. If the result is an array of food, great: we reset the data, reload the table view. If the result is an error, we have to show an error to the user and handle that then.
// FoodLaLaViewController
var dataSource = [Food]() {
didSet {
tableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
getFood()
}
private func getFood() {
FoodService().getFood() { [weak self] result in
switch result {
case .Success(let food):
self?.dataSource = food
case .Failure(let error):
self?.showError(error)
}
}
}
This is a typical pattern for doing API calls. But this whole view controller depends on loading the food array: if the data is not there, or if it is wrong for any reason, it fails. The best way to make sure that the view controller is doing what it is supposed to do with the data is… to do testing.
View Controller Tests?!!! (20:54)
View Controller tests are painful. In this case, it is even more painful because we have this service, asynchronous API call, a completion block, and some resulting enum. That makes it hard to test whether the view controller is doing what it is supposed to do.
// FoodLaLaViewController
var dataSource = [Food]() {
didSet {
tableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
getFood()
}
private func getFood() {
FoodService().getFood() { [weak self] result in
switch result {
case .Success(let food):
self?.dataSource = food
case .Failure(let error):
self?.showError(error)
}
}
}
We first need to have more control of the food service; we need to be able to inject it with either an array of food or an error. We can see the problem: when getFood()
instantiates a food service, our tests have no way of injecting our own. The first test is to add dependency injection.
// FoodLaLaViewController
func getFood(fromService service: FoodService) {
service.getFood() { [weak self] result in
// handle result
}
}
// FoodLaLaViewControllerTests
func testFetchFood() {
viewController.getFood(fromService: FoodService())
// now what?
}
Now our getFood()
function takes the FoodService
and we will have more control and we can do more testing. We have the controller, called getFood
function, and we pass in the FoodService
. But of course, we want to have full control of the FoodService
. How can we take it out?
struct FoodService {
func get(completionHandler: Result<[Food]> -> Void) {
// make asynchronous API call
// and return appropriate result
}
}
This is a value type: you cannot subclass. Instead, we would have to use protocols. The FoodService
has a get function, completionHandler
, which gives the result. You can imagine that every other service in your app, every other API call that needs a get
function (e.g. dessert), will have something similar. It has a completion handler that would take in the result and then parse it.
We can immediately make this more generic:
protocol Gettable {
associatedtype T
func get(completionHandler: Result<T> -> Void)
}
We can use protocols with associated types (a way to do generics in Swift). We say that everything that conforms to the Gettable
protocol has the get function and it takes in a completion handler with the result of this type. In our case, it is going to be food (but in the dessert service, it is going to be dessert; it could be interchangeable).
struct FoodService: Gettable {
func get(completionHandler: Result<[Food]> -> Void) {
// make asynchronous API call
// and return appropriate result
}
}
Going back to the food service, the only change is that it conforms to the Gettable
protocol. The get function is already implemented. It is going to take a completionHandler
that will take in the result of items… because protocols with associated types are smart (the result says array of food, the associated type is an array of food). You do not have to specify it.
Going back to the view controller, this is again exactly the same.
// FoodLaLaViewController
override func viewDidLoad() {
super.viewDidLoad()
getFood(fromService: FoodService())
}
func getFood<S: Gettable where S.T == [Food]>(fromService service: S) {
service.get() { [weak self] result in
switch result {
case .Success(let food):
self?.dataSource = food
case .Failure(let error):
self?.showError(error)
}
}
}
The only difference is that you do have to specify that the associated type is the food array (you do not want our food view controller to be calling dessert service). You want to constrain it, and say that this getFood()
function will only get a result of food items. Otherwise, it is anything that conforms to the Gettable
protocol. This allows us to have more fine control over what we pass in as the FoodService
– because it does not have to be the actual FoodService
, we can inject it with something else.
In our test, we can create a Fake_FoodService
. It has to have two things: 1) conform to the Gettable
protocol, and 2) the associated type has to be a food array.
// FoodLaLaViewControllerTests
class Fake_FoodService: Gettable {
var getWasCalled = false
func get(completionHandler: Result<[Food]> -> Void) {
getWasCalled = true
completionHandler(Result.Success(food))
}
}
It conforms to Gettable
, it takes in the result of food, and returns a food array. Because this is testing, we do want to make sure that the get
function from the Gettable
is called, because it can return and the function could theoretically assign an array of food items from anywhere. We need to make sure that it is called; in this case it is doing success, but you can do the same test with injecting failure to a view controller and make sure that your view controller behaves as expected depending on the result input that you are putting in. This is the test:
// FoodLaLaViewControllerTests
func testFetchFood() {
let fakeFoodService = Fake_FoodService()
viewController.getFood(fromService: fakeFoodService)
XCTAssertTrue(fakeFoodService.getWasCalled)
XCTAssertEqual(viewController.dataSource.count, food.count)
XCTAssertEqual(viewController.dataSource, food)
}
We have the fakeFoodService
: We can inject our fakeFoodService
(that we have more control over), and we can test that get
was called and that the data source that was injected through our FoodService
is exactly the same one as was assigned to the view controller. We have a powerful test for the view controller. At the same time, by adding the Gettable
protocol, we have a framework for all the other services. We can do a deletable, updatable protocol, creatable; looking at the service, you can immediately see which functions it has implemented and easily inject them and test them.
I wrote an example on injecting storyboards and using protocols, and I highly recommend this talk by Alexis Gallagher on protocols with associated types. I may have made it look simple, but protocols with associated types do not often work as expected. You might get frustrated when working with them, and I find this talk to be calming, because he explains its limitations.
Then, you can go back and enjoy popcorn.
Practical POP! Conclusion (27:40)
We talked about how protocols can be used in practical ways in our everyday code for view controllers, views, and networking. This helps you make safer, maintainable, reusable, more uniform, modular code. More testable code. Protocols are amazing to use compared to subclassing.
However, protocols can get too exciting. I probably use too many protocols: I learned them, it is something new and shiny and I want to use them all the time… but they might not be needed. In my first example, when I had a shakable view and a shake function, that was fine. As soon as it was extracted to two views, I needed to re-factor this, and put it in a protocol. Do not go too crazy with them.
To finish, two interesting talks:
-
Beyond Crusty: Real-World Protocols by Rob Napier, on real world protocols. He first starts out with bad code, re-factors it to protocols… and ends up with 10 protocols and 20 lines of code. He solves the problem by using a struct, and only four lines of code. In Swift we have different new things, including powerful enums, structs, and protocols. Depending on the problem, it might need different solutions. I recommend experimenting with protocols but considering, “is this struct enough, or some composition is enough?”.
-
Blending Cultures: The Best of Functional, Protocol-Oriented, and Object-Oriented Programming by Daniel Steinberg. He looks at how you can use everything because we are not constrained to protocols, and we still have object-oriented ideas and functional ideas. Great talk on showing how you can use everything to extract the things that do not change in your code, with the things that change.
As you do your everyday coding, hopefully you will now consider protocols too!
Receive news and updates from Realm straight to your inbox