Defining your own protocols

Swift and UIKit have protocols at the core of their design. You might have noticed this when you were implementing custom UIViewController transitions, or when you worked on a table view or collection view. When you implement these features, you create objects that function as delegates for the transitions, table views, and collection views, and conform them to specific protocols. When you worked on view controller transitions in Chapter 4, Immersing Your Users with Animation, you also implemented an NSObject subclass that conformed to UIViewControllerAnimatedTransitioning.

It's possible for you to define and use your own protocols. Protocols are 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 this book's code bundle.

Let's implement a simple protocol that defines the expectations 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. You should try to follow this convention as much as possible because it makes your code easier to understand for other developers:

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 (a constant) 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 Latin name of PetType.

Whenever you define that a protocol requires a certain variable to exist, 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 usually would, but you stop at the first curly bracket. You only write down the method signature.

A protocol can also require that the implementer has 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 that the pet belongs to, so implementing this as a property of the object rather than the instance makes a lot of sense.

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

To do this in OOP, you would 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 would 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 will 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 functionalities to a certain class because the inheritance hierarchy gets in the way after a while.

Let's see what it looks like when you use the PetType protocol to solve this challenge without using inheritance at all:

protocol PetType {
  var name: String { get }
  var age: Int { get set }

  func sleep()

  static var latinName: String { get }
}

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 the Cat and Dog objects and makes them take a nap. Instead of checking for a class, the code checks that the pet that is passed in conforms to the PetType protocol, and if it does, its sleep() method can be called because the protocol dictates that any PetType must implement a 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