Chapter 6.  Avoiding Complex Inheritance with Protocols

If you've been around a Swift developer long enough, you must have heard them mention Protocol Oriented Programming at least once. This programming paradigm was introduced by Apple at 2015's WWDC in a talk that generated a lot of buzz among developers. Suddenly, we learned that thinking in classes and hierarchy leads to code that's hard to maintain, change, and expand. The talk introduced a way of programming that is focused around what an object can do instead of explicitly caring about what an object is.

This chapter will demonstrate to you how you can make use of the powers of Protocol Oriented Programming, and it will show you why it's an important feature of Swift. We'll start off with some simple use cases, and then we'll shortly take a deep dive into its associated types and generic protocols.

Understanding these design patterns and recognizing situations in which a protocol, protocol extension, or a generic protocol can help you improve your code will lead to code that is not only easier to maintain but also a joy to work with. The structure for this chapter is as follows:

  • Defining your own protocols
  • Checking for traits, not types
  • Extending your protocols with default behavior
  • Improving your protocols with associated types

By the end of this chapter, you might feel a bit overwhelmed and amazed. Don't worry, shifting your mindset from OOP to a Protocol-Oriented approach isn't easy by any means. It's an entirely different way of thinking about structuring your code that will take some getting used to. It's time to dive right in by defining some of your own protocols.

Defining your own protocols

Throughout UIKit, protocols are first-class citizens. You might have noticed this when you were implementing the custom UIViewController transitions. When you implemented these transitions, you had to create an object that functioned as a delegate for the transition and conformed to the UIViewControllerTransitioningDelegate protocol. You also implemented an NSObject subclass that conformed to UIViewControllerAnimatedTransitioning.

It's possible for you to define and use your own protocols. This usage is not confined to delegate behavior only. Defining a protocol is very similar to defining a class, struct, or enum. The main difference is that a protocol does not implement or store any values on its own. It acts as a contract between whoever calls an object that conforms to a protocol and the object that claims to conform to the protocol.

Create a new Playground (File | New... | Playground) if you want to follow along or check out the Playground in the book's Git repository.

Let's implement a simple protocol of our own that establishes a baseline for any object that claims to be a pet. The protocol will be called the PetType protocol. Many protocols defined in UIKit and the Swift standard library use either Type, Ing, or Able as a suffix to indicate that the protocol defines a behavior rather than a concrete type. We should try to follow this convention as much as possible:

protocol PetType { 
    var name: String { get } 
    var age: Int { get set } 
     
    func sleep() 
     
    static var latinName: String { get } 
} 

The definition for PetType states that any object that claims to be PetType must have a get-only variable called name, an age that can be changed because it specifies both get and set, a method that makes the pet sleep, and finally, a static variable that describes the PetType's Latin name.

Whenever you define that a protocol requires a certain variable to be present, you must also specify whether the variable should be gettable, settable, or both. If you specify that a certain method must be implemented, you write the method just as you normally would, but you stop at the first curly brace. You only write down the method signature.

A protocol can also require that the implementer adds a static variable or method. This is convenient in the case of PetType because the Latin name of a pet does not necessarily belong to a specific pet but to the entire species of pet that the pet belongs to, so implementing this as a property of the object rather than the instance makes a lot sense.

To demonstrate how powerful a small protocol such as this can be, we will implement two pets: a cat and a dog. Also, we'll write a function that takes any pet and then makes them take a nap by calling the sleep method.

If you did this in OOP, you might create a class called Pet and then you'd create two subclasses, Cat and Dog. The nap method would take an instance of Pet, and it could look a bit like this:

func nap(pet: Pet) { 
    pet.sleep() 
} 

The object-oriented approach is not a bad one. Also, on such a small scale, no real problems would occur. However, when the inheritance hierarchy grows, you typically end up with base classes that contain methods that are only relevant to a couple of subclasses. Alternatively, you will find yourself unable to add certain functionality to a certain class because the inheritance hierarchy simply gets in the way after a while. Let's improve this simple example using a PetType protocol to solve this challenge without using inheritance at all:

struct Cat: PetType { 
    let name: String 
    var age: Int 
     
    static let latinName: String = "Felis catus" 
     
    func sleep() { 
        print("Cat: ZzzZZ") 
    } 
} 
 
struct Dog: PetType { 
    let name: String 
    var age: Int 
     
    static let latinName: String = "Canis familiaris" 
     
    func sleep() { 
        print("Dog: ZzzZZ") 
    } 
} 
 
func nap(pet: PetType) { 
    pet.sleep() 
} 

We just managed to implement a single method that can take both our Cat and Dog and make them take a nap. Instead of checking for a concrete type, we're checking that the pet we're passing in conforms to the PetType protocol, and if it does, we can call its sleep method. This brings us to the next topic of this chapter, checking for traits instead of types.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.139.50