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:
- 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. - Defines a property on the protocol called
numberOfWheels
. The property is of typeInt
, which means it should represent the number of wheels the vehicle has. By including this requirement in the protocol, any type conforming toVehicle
must provide an implementation of this property. - Defines a property called
maxSpeed
that is aDouble
. This property indicates the maximum speed the vehicle can achieve. - Defines a property on the protocol called
totalDistanceTraveled
that is aDouble
. Note that this property is read-write because it specifies bothget
andset
.numberOfWheels
andmaxSpeed
are read-only because they only specify theget
keyword on the property. This means that you can mutate (that is, change)totalDistanceTraveled
. SincenumberOfWheels
andmaxSpeed
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. - Defines a function on the protocol,
showDetails()
, that returns aString
that provides details about the vehicle when executed. - Defines a function,
move(direction:duration:speed:)
, that takesDirection
,TimeInterval
andDouble
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:
- Defines a new class called
FamilyCar
that conforms to theVehicle
protocol. Conforming to a protocol looks similar to subclassing a type. In essence,FamilyCar
will adopt the behavior and requirements thatVehicle
specifies. - Define each property as required by the protocol. They need to be implemented according to the protocol’s specifications.
- Initialize the properties of the class. The
init()
method is the initializer of theFamilyCar
class. - 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 theFamilyCar
. - 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 ofVehicle
s.
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:
- Defines a new class called
VehicleType
that’s open so it can be subclassed. - Defines the properties; these match the properties in the protocol.
- 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. - Implement an initializer.
- Defines
move(direction:duration:speed:)
, which matches the function in the protocol. - 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:
- Defines a new class,
FamilyCarInheritance
, that inherits fromVehicleType
. - Defines an immutable property,
carMaxSpeed
, to hold the maximum speed of the car. This ensures it can’t be mutated. - Overrides the
maxSpeed
property to return thecarMaxSpeed
property. This allows you to set a different maximum speed than the superclass. - Overrides
move(direction:duration:speed:)
to call the superclass implementation, then print the current distance traveled. - Overrides
showDetails()
to return the correct description for the car.