Chapter 35: Protocol-Oriented Programming

*If you like Object-Oriented Programming, then you will love Protocol-Oriented Programming. Prepare your mind to drive down an exciting new avenue in the programming world.*


What you will learn

  • Overview of Swift Types
  • Writing your first Protocol
  • Conforming to a Protocol
  • Reference types & protocols
  • Protocol Extensions

Key Terms

  • Protocol
  • Extension
  • Value Type
  • Reference Type
  • Struct (Structure)

In 2015, there was a talk given at Apple's WWDC event simply called "Protocol-Oriented Programming in Swift" by Dave Abrahams, professor of "Blowing Your Mind". His confident title ended up working in his favor because over the duration of that 45 minute talk, he proceeded to blow the minds of every developer in attendance. This talk is known as the "Crusty" talk based on the imaginary old-school developer named Crusty who doesn't like newfangled programming technologies or "fads". Throughout the talk, Dave proceeds to show Crusty the benefits of using protocols and how Swift truly is a protocol-oriented programming language.

In this chapter, you will read a brief overview of Protocol-Oriented Programming (POP) and learn how it can be useful as a developer.

Overview of Swift Types

The Swift programming language uses named and compound types as you'd expect. Figure 5.2.1 shows these types:

Figure 5.2.1 swift-types.png

In addition to being named types, Primitives like Int, String, and Float in addition to structures (structs) and enums (enumerations) are also value types. This means that if you create a value of one of these types, that value is copied when it is assigned to a constant or variable. It is also copied when passed to a function or method. Every time a new copy of that value is created, the new copy is used not the original version. Multiple unique copies can be made.

This is in stark contrast to a reference type. Classes are reference types. If we create a class, that value is saved and if we assign the reference to a variable or constant we actually copy in the original value. If we modify the value in the variable we made, we modify the original value. After we copy it, we've made a shared instance of the same value.

Figure 5.2.2 shows this in action:

Figure 5.2.2 referencetypesvsvaluetypes.png

As you can see on the left, there is a class named Car which is a reference type. There is also a variable called car1 and it is instantiated as a copy of the class Car. Beneath that, a variable called car2 is created and instantiated as a copy of car1. At the bottom of the image, car2's color property is set to be red and car1's color property is also set to red. This is because we've used a reference type and by changing the color of car2, we also change the color of car1.

On the right-hand side, there is a struct which is a value type. You can see the same code is used to create car1 and car2, but if you look at the bottom you can see that I set the color property of car2 to be red, while the color property of car1 is blue. With value types, each copy is a unique copy that exists independently. This allows us to give each copy different values.

As you can imagine, the more complex our code becomes, the more room there is for error with reference types since you need to remember that any copy you make will modify the original. Value types have their problems as well, but reference types need much more maintenance. Apple reccomends that developers use value types (i.e. struct, enum, etc.) whenever possible.

Getting Started

With that topic under our belt, we now have a foundation to begin unpacking POP. We will be doing this lesson in a Playground, simply because this is an introductory chapter to POP and we could go into many chapters about how to implement this into your apps. But really, dig in to this stuff. If you find it as interesting as I do, that won't be a challenge.

Okay, let's get down to business.

First, open Xcode if you haven't already and click Create New Playground. Give it a name like Protocol-Oriented Fun and click Next. Choose somewhere to save this .playground file and click Create to save it. You should see a screen like the one in Figure 5.2.3.

Figure 5.2.3 Screen Shot 2016-10-10 at 5.01.11 AM.png

Delete all the boilerplate code on the left side but leave import UIKit as it is necessary.

Writing Your First Protocol

We're going to write a protocol for a spaceship. At the bottom of your Playground file, type the following:

protocol Spaceship {
    var isFlying: Bool { get set }
}

In this protocol, we declared a property called isFlying of type Boolean. In protocols, you need to define whether it is read/write or read-only. We've set it to be read/write because we've used { get set }. If we wanted to make it a read only property, we could have simply used { get }.

We will now define a few functions, but there is a bit of a caveat when it comes to protocols. Think of any functions we choose to include in our protocol as part of a contract meaning anything that conforms to the protocol will need to implement all of the functions in the "contract". We won't include any implementation code or even a set of brackets. We are simply telling our protocol what functions to require of anything that conforms to it.

Add the following functions for our spaceship:

protocol Spaceship {
    var isFlying: Bool { get set }
    func launch()
    func land()
}

The code above actually won't work just yet. Another thing to know is that when working with value types, we need to specify that our functions can mutate the properties inside a class, structure, or even other value types. To make this change, simply add the keyword mutating before the function's declaration:

protocol Spaceship {
    var isFlying: Bool { get set }
    mutating func launch()
    mutating func land()
}

Conforming To A Protocol

So now we have a protocol that contains the property isFlying in addition to two functions for launching and landing. But what good is it if nothing is going to use it? Let's create a structure called TIEFighter that will conform to our protocol. Beneath the Spaceship protocol, write the following:

...

struct TIEFighter: Spaceship {

}

You probably noticed that your Playground window threw an error. The error says, "Type 'TIEFighter' does not conform to protocol 'Spaceship'"

Interesting! How do we make it conform? Simple. We need to honor the contract we wrote in the protocol which included isFlying, launch(), and land() by adding them into our structure.

struct TIEFighter: Spaceship {
    var isFlying: Bool = false

    mutating func launch() {
        if isFlying {
            print("Already launched!")
        } else {
            isFlying = true
            print("BLAST OFF!")
        }
    }

    mutating func land() {
        if isFlying {
            isFlying = false
            print("Silence...")
        } else {
            print("Already landed!")
        }
    }
}

As you can see, our error has vanished! We have now conformed to all of the requirements of the contract we set in the protocol Spaceship.

A place you've already used protocols is in apps with UITableView. We actually conform to the protocols UITableViewDelegate and UITableViewDataSource for using UITableView. You will encounter errors unless you honor the contract set in the UITableView protocols by conforming to certain functions.

Reference Types Can Conform, Too!

Why should value types have all the fun? Reference types like classes can conform to protocols as well. To illustrate this point, write the following code at the bottom of your Playground file:

class MilleniumFalcon: Spaceship {
    var isFlying: Bool = false

    func launch() {
        if isFlying {
            print("Great, kid. Don't get cocky.")
        } else {
            isFlying = true
            print("Punch it!")
        }
    }

    func land() {
        if isFlying {
            isFlying = false
            print("You know, sometimes I amaze even myself...")
        } else {
            print("Chewie, we're home.")
        }
    }
}

Our class MilleniumFalcon conforms to the Spaceship protocol by implementing the properties and functions required by it. But that doesn't mean we can't add in extra functionality. Let's put the fun in functionality and add in the following function to the MilleniumFalcon class:

class MilleniumFalcon: Spaceship {
    var isFlying: Bool = false

    func launch() {
        if isFlying {
            print("Great, kid. Don't get cocky.")
        } else {
            isFlying = true
            print("Punch it!")
        }
    }

    func land() {
        if isFlying {
            isFlying = false
            print("You know, sometimes I amaze even myself...")
        } else {
            print("Chewie, we're home.")
        }
    }

    func fireLasers() {
        print("PEW PEW PEW!")
    }
}

Cool, so now we have a struct called TIEFighter and a class called MilleniumFalcon both of which conform to the protocol Spaceship. Let's create an instance of each. At the bottom of your Playground file add the following code:

var tieFighter = TIEFighter()
var milleniumFalcon = MilleniumFalcon()

Now let's call some of the functions declared in these instances to see how they work:

var tieFighter = TIEFighter()
var milleniumFalcon = MilleniumFalcon()

tieFighter.launch() // isFlying set to true / prints "BLAST OFF!"
milleniumFalcon.launch() // isFlying set to true / prints "Punch it!"
milleniumFalcon.fireLasers() // prints "PEW PEW PEW!"

As you can see, our functions work as expected. We could have also called our land() function for both the TIE Fighter and the Millenium Falcon, but we didn't.

Next, we are going to create an array and fill it with things that conform to our Spaceship protocol. Then we will loop through the array and print each element. Add the following to the bottom of your Playground file:

var spaceshipArray: Array<Spaceship> = [tieFighter, milleniumFalcon]

for spaceship in spaceshipArray {
    print("\(spaceship)")
}

/* Prints:
TIEFighter(isFlying: false)
MilleniumFalcon
*/

As you can see, it doesn't print the same thing for the struct and the class. We have yet to set a description and by default we don't have one. To add a description we can actually set our Spaceship protocol to conform to another protocol called CustomStringConvertible, which can give it the extra functionality we want. Let's conform to it now!

Protocolception: A Protocol Within A Protocol

OK, so we aren't really putting a protocol inside of another protocol, so to speak, but we will be conforming to another protocol so that we can add some functionality.

Go to the very top of your Playground file and in the Spaceship protocol, set it to conform to CustomStringConvertible like so:

protocol Spaceship: CustomStringConvertible {
    var isFlying: Bool { get set }
    mutating func launch()
    mutating func land()
}

Now any structure or class that conforms to the protocol Spaceship also has to conform to the protocol CustomStringConvertible which has one required property: description.

You should be seeing two errors. One in the struct TIEFighter and one in the class MilleniumFalcon. Both have to do with the fact that we have not yet conformed to the protocol CustomStringConvertible. To do so, we are going to enter the TIEFighter struct, create a variable called description, and set return values like so:

struct TIEFighter: Spaceship {
    var isFlying: Bool = false

    var description: String {
        if isFlying {
            return "TIE Fighter is flying"
        } else {
            return "TIE Fighter is not flying"
        }
    }

    mutating func launch() {
        if isFlying {
            print("Already launched!")
        } else {
            isFlying = true
            print("BLAST OFF!")
        }
    }

    mutating func land() {
        if isFlying {
            isFlying = false
            print("Silence...")
        } else {
            print("Already landed!")
        }
    }
}

Do the same in the MilleniumFalcon class:

class MilleniumFalcon: Spaceship {
    var isFlying: Bool = false

    var description: String {
        if isFlying {
            return "The Millenium Falcon is flying"
        } else {
            return "The Millenium Falcon is not flying"
        }
    }

    func launch() {
        if isFlying {
            print("Great, kid. Don't get cocky.")
        } else {
            isFlying = true
            print("Punch it!")
        }
    }

    func land() {
        if isFlying {
            isFlying = false
            print("You know, sometimes I amaze even myself...")
        } else {
            print("Chewie, we're home.")
        }
    }

    func fireLasers() {
        print("PEW PEW PEW!")
    }
}

Since our struct and class both conform to the protocol Spaceship, they are also required to conform to CustomStringConvertible. Now both the class and struct have a description property thanks to CustomStringConvertible.

Back before we created a loop and walked through our array of spaceships, two different description values were printed to the console because there was no specific description set and it was printing the default description.

Let's change our loop at the bottom of our Playground window to print our new descriptions:

var spaceshipArray: Array<Spaceship> = [tieFighter, milleniumFalcon]

for spaceship in spaceshipArray {
    print("\(spaceship.description)")
}

/* Prints:
TIE Fighter is not flying
The Millenium Falcon is not flying
*/

It works! Pretty cool. So far, we've learned the basics of how protocols work in Swift 3. But where protocols become very powerful is in protocol extensions. Announced in Swift 2, they expanded the capabilities of protocols for added functionality and more. You're about to read a brief overview of protocol extensions and how to use them in code.

Protocol Extensions in Swift 3

This is where the real magic happens. 🔮 Protocol Extensions were introduced in 2015 when Apple launched Swift 2 and they are so powerful. Not only can you extend functionality in your own protocols, but you can even extend the protocols predefined in the Swift Standard Library. Protocol extensions were introduced to help soothe the pains of working with value types, since there is no inheritance like in reference types. In a protocol extension, you can write implementation code which can be used by any class or struct that conforms to it's related protocol.

Let's dive back in where we left off above in this chapter.

Extending The Spaceship Protocol

Before we begin, we're going to add two new properties (make and model) to our protocol. We will use these later in our protocol extension. Write the following code inside of the Spaceship protocol:

protocol Spaceship {
    var isFlying: Bool { get set }
    var make: String { get set }
    var model: String { get set }
    mutating func launch()
    mutating func land()
}

Now we have two properties for make and model. We will now create the body of our protocol extension. It is as simple as using the keyword extension and calling the name of the protocol that you want to extend. This is similar to writing a type extension if you've done that before. Beneath the closing bracket of the Spaceship protocol, write the following:

extension Spaceship {

}

BOOM. Done. We will come back to adding in code to this later. Now, just like before, we have some errors in our TIEFighter and MilleniumFalcon class. This is because we are no longer conforming properly to Spaceship as we have added some new properties. We need to create properties for make and model in both the struct and class.

Do so like this:

struct TIEFighter: Spaceship {
    var isFlying: Bool = false
    var make: String
    var model: String

    // Other code redacted for spacing purposes.
}

Notice how a struct doesn't require the variables to be instantiated as they are initialized when a new copy of TIEFighter is created. And now add the same thing to our class:

class MilleniumFalcon: Spaceship {
    var isFlying: Bool = false
    var make: String
    var model: String

    // Other code redacted for spacing purposes.
}

AHH! 😱 Another error. We fixed one and made another. If you look at the error description, you will see that it is because we have no initializers. The class MilleniumFalcon doesn't have any initializers, so we will need to create one. Add the following code beneath where you just created make and model:

class MilleniumFalcon: Spaceship {
    var isFlying: Bool = false
    var make: String
    var model: String

    init(isFlying: Bool, make: String, model: String) {
        self.isFlying = isFlying
        self.make = make
        self.model = model
    }

    // Other code redacted for spacing purposes.
}
...

Because we have changed so much in our class and struct, you will see a lot of other errors near the bottom of the Playground file. We will come back to this later on, so for now just comment out the following code:

/*
var tieFighter = TIEFighter()
var milleniumFalcon = MilleniumFalcon()

tieFighter.launch() // isFlying set to true / prints "BLAST OFF!"
milleniumFalcon.launch() // isFlying set to true / prints "Punch it!"
milleniumFalcon.fireLasers() // prints "PEW PEW PEW!"

var spaceshipArray: Array<Spaceship> = [tieFighter, milleniumFalcon]

for spaceship in spaceshipArray {
    print("\(spaceship.description)")
}
*/

Alright, with those errors silenced for the moment, we need to go add some implementation code to our protocol extension. Scroll back up to the top of your Playground file and inside of the Spaceship extension, write the following:

extension Spaceship {
    mutating func launch() {
        if isFlying {
            print("Already flying!")
        } else {
            isRunning = true
            print("\(self.description) blasted off!")
        }
    }

    mutating func land() {
        if isFlying {
            isFlying = false
            print("\(self.description) landed.")
        } else {
            print("Already landed!")
        }
    }
}

The reason we added these functions into our protocol extension is because we're actually implementing them twice which is bad programming. We used the same code inside the launch() and land() functions within our struct and class. By placing this code in our protocol extension, now both TIEFighter and MilleniumFalcon can access this function and implement it without it needing to be repeated. This also means that if we add any other structs or classes, we can easily use the launch() and land() functions with them.

Now that we've added these functions to our protocol extension, we need to clean up our struct and class. Remove the functions launch() and land() from both the struct TIEFighter and the class MilleniumFalcon.

Even though the functions are no longer within our class or struct, they still have access to them via the protocol extension. As you can imagine, being able to extend protocols becomes extremely useful when wanting a certain functionality that the Swift Standard Library doesn't offer.

Making Our Description Property More Descriptive

Earlier in this chapter, we created a property for our spaceship's make and model. Let's put those to use. We're going to add a property to our protocol extension and change up the way we used description in both our class and struct.

At the top of the extension, add the following property:

extension Spaceship {
    var makeModel: String {
        return "\(make) \(model)"
    }

    mutating func launch() {
        if isFlying {
            print("Already flying!")
        } else {
            isRunning = true
            print("\(self.description) blasted off!")
        }
    }

    mutating func land() {
        if isFlying {
            isFlying = false
            print("\(self.description) landed.")
        } else {
            print("Already landed!")
        }
    }
}

Since TIEFighter and MilleniumFalcon have to conform to the properties and functions in our protocol/extensions, we can use the make and model properties to construct a value of makeModel. makeModel is a String value that combines both make and model.

Now all we need to do to create a specific description property for both the struct and class, we simply need to return makeModel. Inside of the description property for TIEFighter and MilleniumFalcon, delete all current code and replace it with this:

var description: String {
    return self.makeModel
}

Now, using our extension's functionality, we can return the make and model values that we set when we instantiate our class/struct.

Re-Instantiating Our Structs & Classes

Let's go ahead and uncomment the code we commented earlier. We're going to need to change how we instantiate tieFighter and milleniumFalcon, though. When we first wrote this code, we didn't have any initializer or a need to initialize any values. Since we added a make and model property the way we did, we need to initialize their values now.

For tieFighter, we initially wrote:

var tieFighter = TIEFighter()

But we need to change this to:

var tieFighter = TIEFighter(isFlying: false, make: "TIE", model: "Fighter")

Likewise, in our milleniumFalcon variable, we first wrote:

var milleniumFalcon = MilleniumFalcon()

We need to update it to say:

var milleniumFalcon = MilleniumFalcon(isFlying: false, make: "Millenium", model: "Falcon")

If you look at the code beneath these variables where we called our launch() and fireLasers() functions earlier, you should see that they are working again! You'll also notice that on the right-hand side of the Playground window where the console prints, the names of the ships we've made based on their description property. The description property is being created by combining the make and model property passed in when we instantiate the class/struct.

You should also see in the bottom console panel that our launch() and fireLasers() functions are successfully using the description to print:

TIE Fighter blasted off!
Millenium Falcon blasted off!
PEW PEW PEW!

Pretty amazing! We've written an extension implementing a couple functions and a property to build the make and model of our spaceship and it's working great!

We can also modify our for loop so that it uses makeModel from our extension like so:

var spaceshipArray: Array<Spaceship> = [tieFighter, milleniumFalcon]

for spaceship in spaceshipArray {
    print("\(spaceship.makeModel)")
}

As you should be able to see, this for loop should print out the make and model name in the console. We have access to the makeModel property thanks to our handy dandy protocol extension!

Wrapping up

This chapter has been a brief overview of Protocol-Oriented Programming in Swift. Although there were probably a lot of new ideas and concepts in this chapter, it is really only the tip of the iceberg. POP is a deep and wide topic and is actually an entirely new way to approach programming. While there are still places where classes and reference types are useful, POP aims to reduce the use of classes and increase the use of value types and protocols.

There are lots of fantastic ways to use POP in your apps and I highly suggest that you research and dig deeper into this topic. It's new and fascinating and I only foresee it's popularity increasing from here on out.

Exercise

Create a class named StarKillerBase and set it to conform to the Spaceship protocol. Ensure that it conforms to Spaceship by adding in the necessary properties and methods. Add any extra functions you think StarKiller Base might need such as obliteratePlanets(planetName:_).

Add it as an item into your spaceshipArray and ensure that it is printed via it's makeModel property.