Instruction

As you learned in a previous lesson, protocols create a blueprint for defining how a type should look from an API perspective. In a protocol, you define the properties and methods that a type should have. Any type that conforms to that protocol must implement those properties and methods. This enables you to interact with a protocol without requiring knowledge of the actual type.

Defining a Protocol

You define a protocol in Swift with the protocol keyword. For example, this code defines a protocol for a vehicle:

// 1
protocol Vehicle {
  // 2
  var numberOfWheels: Int { get }
  // 3
  var maxSpeed: Double { get }
  // 4
  var totalDistanceTraveled: Double { get set }
  // 5
  func showDetails() -> String
  // 6
  func move(direction: Direction, duration: TimeInterval, speed: Double)
}

Here’s a breakdown of the code snippet:

  1. Defines a protocol called Vehicle that types can conform to. Any type that adopts this protocol must provide implementations for the properties and functions defined within it.
  2. Defines a property on the protocol called numberOfWheels. The property is of type Int, which means it should represent the number of wheels the vehicle has. By including this requirement in the protocol, any type conforming to Vehicle must provide an implementation of this property.
  3. Defines a property called maxSpeed that is a Double. This property indicates the maximum speed the vehicle can achieve.
  4. Defines a property on the protocol called totalDistanceTraveled that is a Double. Note that this property is read-write because it specifies both get and set. numberOfWheels and maxSpeed are read-only because they only specify the get keyword on the property. This means that you can mutate (that is, change) totalDistanceTraveled. Since numberOfWheels and maxSpeed are read-only — also known as immutable — you cannot change them once you’ve set their initial value. This allows you to ensure properties that shouldn’t change can never be changed.
  5. Defines a function on the protocol, showDetails(), that returns a String that provides details about the vehicle when executed.
  6. Defines a function, move(direction:duration:speed:), that takes Direction, TimeInterval and Double as parameters. Direction, in this case, could be a simple enum describing the direction of travel.

Now, when you refer to a Vehicle in your code, you know it has these properties and functions available for you to use. The protocol doesn’t define whether the properties need to be computed properties or stored properties. That’s up to the type that conforms to the protocol.

Conforming to a Protocol

You define a type that implements the protocol as follows:

// 1
class FamilyCar: Vehicle {

  // 2
  let maxSpeed: Double
  let numberOfWheels: Int
  var totalDistanceTraveled: Double
  
  // 3
  init() {
    self.maxSpeed = 50.0
    self.numberOfWheels = 4
    self.totalDistanceTraveled = 0.0
  }
  
  // 4
  func move(direction: Direction, duration: TimeInterval, speed: Double) {

  }
  
  // 5
  func showDetails() -> String {
    "I am a family car"
  }
}

The code demonstrates how to define a type that adheres to the Vehicle protocol. Now, you’ll break down the different components of this process:

  1. Defines a new class called FamilyCar that conforms to the Vehicle protocol. Conforming to a protocol looks similar to subclassing a type. In essence, FamilyCar will adopt the behavior and requirements that Vehicle specifies.
  2. Define each property as required by the protocol. They need to be implemented according to the protocol’s specifications.
  3. Initialize the properties of the class. The init() method is the initializer of the FamilyCar class.
  4. Implement move(direction:duration:speed:) as the protocol requires. This implements the function required by the protocol; it will handle moving the car in your program. In this case, you’ll track the distance traveled by the FamilyCar.
  5. Implement showDetails() as the protocol requires with a simple implementation. This function just returns a sensible description for the type to make it easy to distinguish between different types of Vehicles.

You’ve now created a class called FamilyCar and defined it to conform to the Vehicle protocol. By adhering to the protocol, the class must provide implementations for all the properties and functions specified by the protocol. This adherence allows you to create instances of FamilyCar that share a common interface with other types conforming to the same protocol, enabling seamless interactions and code reusability.

Implementing With Inheritance

Now, compare how you’d implement this using inheritance. To start, you’d define a superclass that matches the protocol:

// 1
open class VehicleType {
  // 2
  let numberOfWheels: Int
  // 3
  var maxSpeed: Double
  var totalDistanceTraveled: Double

  // 4
  init() {
    self.numberOfWheels = 4
    self.maxSpeed = 100
    self.totalDistanceTraveled = 0.0
  }

  // 5
  func move(direction: Direction, duration: TimeInterval, speed: Double) {
    
  }

  // 6
  func showDetails() -> String {
    "I am a vehicle"
  }
}

Here’s what the superclass does:

  1. Defines a new class called VehicleType that’s open so it can be subclassed.
  2. Defines the properties; these match the properties in the protocol.
  3. Note that maxSpeed must be a variable if you want to override it in a subclass. This may not be desirable if, semantically, you want a read-only property.
  4. Implement an initializer.
  5. Defines move(direction:duration:speed:), which matches the function in the protocol.
  6. Defines showDetails(), which also matches the function in the protocol.

Now, implement a new family car using inheritance:

// 1
class FamilyCarInheritance: VehicleType {
  // 2
  let carMaxSpeed = 50.0
  // 3
  override var maxSpeed: Double {
    get {
      return carMaxSpeed
    }
    set {
      // Do nothing
    }
  }

  // 4
  override func move(direction: Direction, duration: TimeInterval, speed: Double) {
    super.move(direction: direction, duration: duration, speed: speed)
    print("Current distance is \(totalDistanceTraveled)")
  }

  // 5
  override func showDetails() -> String {
    "I am a family car"
  }
}

This does the following:

  1. Defines a new class, FamilyCarInheritance, that inherits from VehicleType.
  2. Defines an immutable property, carMaxSpeed, to hold the maximum speed of the car. This ensures it can’t be mutated.
  3. Overrides the maxSpeed property to return the carMaxSpeed property. This allows you to set a different maximum speed than the superclass.
  4. Overrides move(direction:duration:speed:) to call the superclass implementation, then print the current distance traveled.
  5. Overrides showDetails() to return the correct description for the car.
See forum comments
Download course materials from Github
Previous: Introduction Next: Demo 1