LEARN SWIFT

Buy the PDF

15.1 Defining and conforming to a protocol

Protocols are similar to interfaces in other languages. A protocol specifies a set of behaviours that something should implement, without providing the implementation for those behaviours.

Any class, structure or enumeration can conform to a protocol by implementing the methods and variables defined in the protocol definition.

Protocols are declared using the protocol keyword. When defining a protocol, we provide the type signatures for any methods that the protocol should implement and we specify the types of any properties that should be implemented along with whether they are should implement both get and set methods or only get. Any type that implements the functionality specified in a protocol is said to conform to the protocol.

 1 protocol Animal{
 2     var lives:Int { get set }
 3     var limbs:Int { get }
 4     func makeNoise() -> String
 5 }
 6 
 7 class Cat: Animal{
 8     var lives = 9
 9     var limbs = 4
10 
11     func makeNoise() -> String {
12         return "meow"
13     }
14 }
15 
16 let cat = Cat() // {lives 9 limbs 4}
17 cat.makeNoise() // "meow"
18 cat.lives       // 9
19 cat.lives = 8   // {lives 8 limbs 4}
20 cat.lives       // 8

This example declares a protocol called Animal and specifies that all types that conform to the protocol must implement a method makeNoise that returns a string, along with two integer properties lives and limbs. For lives we have declared that it must provide both get and set whereas limbs can be read only (only get is required).

The syntax for adopting a protocol is similar to that for inheriting from another class i.e. a : after the type name followed by the protocol name. In our example above we define a Cat class which conforms to the Animal protocol (line 7). To do this, the Cat class provides an implementation for the makeNoise method and implements integer properties lives and limbs.

It doesn’t matter how you implement a property when conforming to a protocol so long as you satisfy the get/set requirements for the protocol. i.e. your properties can be either stored or computed so long as the required get and set are available. So in the example above I can just implement both properties as stored properties to conform to the protocol.

When specifying a type method in a protocol we always prefix it with the keyword class, even if the type method is for a structure or enumeration (remember that we use class when defining type methods on classes, but static when defining type methods on structures and enumerations).

You can specify initializers using syntax similar to how you would define an intitializer, just without providing the implementation. I.e. using init along with listing its arguments and their types.

15.2 Optional methods and properties

When conforming to a protocol, by default you are expected to implement all the required methods and properties. However you can mark some as optional but there are some limitations.

Optional properties and methods can be declared using the optional keyword, but it can only be used on protocols that are tagged with the @objc keyword1 (even if you are not planning on interacting with Objective-C code).

Using @objc also imposes additional limitations.

  • @objc protocols can only be used by classes, not by structures or enumeratios.
  • Your class must inherit from an Objective-C class (e.g. NSObject)
  • If your protocol inherits from another protocol, it can only be from one that is also declared using @objc.
  • Your protocol can only use data types that map to Objective-C data types. So arrays and dictionaries are allowed, tuples are not.

Here’s an example that includes an optional property in our protocol definition.

@objc protocol RunningAnimal{
    var limbs:Int { get }
    var topSpeed:Int{ get }
    optional var lives:Int { get }
}

class Cheetah:NSObject, RunningAnimal{
    var limbs = 4
    var topSpeed = 120
}

Even though our Cheetah class here doesn’t provide a lives property, it still conforms to the RunningAnimal protocol. We’ve indicated that that property is an optional property of the protocol so types that adopt the protocol aren’t required to implement that property.

15.3 Inheritance

A class can inherit from 1 superclass but it can conform to multiple protocols. If it is inheriting from another class, then that class name is placed first in the list, before any protocols.

class MyClass: MySuperClass, MyProtocol1, MyProtocol2
    // ...
end

A protocol itself can inherit from multiple other protocols. It will inherit the requirements of all those protocols and can then specify additional requirements of its own.

protocol MyProtocol: MySuperProtocol, Comparable, Equatable, Printable{
    // ...
}

15.4 Protocols are types

Even though protocols don’t have an implementation, they are a type and can be used in places where a type is expected. E.g.

  • As the return type for a function
  • As the parameter type for a function argument
  • As the type of a variable or constant
  • As the type of a collection

So if you declare that a variable has a type of MyProtocol, you can only assign objects that adopt MyProtocol to that variable.

We can also use a protocol as the type for a collection. Thus we can store objects of different types in an array so long as they all adopt a particular protocol. This allows us to implement duck-typing.

In this example we have objects of two different types (Duck and Dog), but since they both conform to the Noisy protocol we can store them in an array that has type Noisy and iterate through them with a for loop.

protocol Noisy{
    func makeNoise() -> String
}

class Dog:Noisy{
    func makeNoise() -> String {
        return "Woof"
    }
}

class Duck:Noisy{
    func makeNoise() -> String {
        return("Quack")
    }
}

var animals:[Noisy] = [Dog(), Duck()]
for a in animals{
    print(a.makeNoise())
}                              // prints "Woof" and "Quack"

You can also require conformance to multiple protocols in places where types are expected. For example, lets say we have a function that accepts as an argument anything that adopts both the Noisy protocol and the Printable protocol. We do this by using the protocol keyword as the parameter type followed by a list of all the protocols that the item passed must conform to in angle brackets.

protocol Noisy{
    func makeNoise() -> String
}
protocol Describable{
    func desc() -> String
}

class Dog:Noisy{
    func makeNoise() -> String {
        return "Woof"
    }
}

class DescribableDog:Noisy,Describable{
    func makeNoise() -> String {
        return "Woof"
    }
    func desc() -> String {
        return "Dog"
    }
}

func describe(item: protocol<Noisy, Describable>) -> String{
    return("\(item.desc()) says \(item.makeNoise())")
}

describe(Dog()) // error: Dog() doesn't conform to Describable
describe(DescribableDog()) // "Dog says Woof"

In this example we declare a function (describe) that takes as its argument anything that conforms to both the Noisy and Describable protocols. If we pass it anything that doesn’t conform to both of those protocols we’ll get an error.

15.5 Example: Implementing Comparable

Comparable is a built-in protocol that is used to allow items to be compared using comparison operators.

Open a playground, type Comparable and then ⌥ -⌘–click (i.e. CMD-OPT-click) on on Comparable. This opens up a header file of Swift definitions at the point where the Comparable protocol is defined. You’ll see some comments about the protocol and the methods that the protocol must implement.

/// Instances of conforming types can be compared using relational
/// operators, which define a `strict total order
/// <http://en.wikipedia.org/wiki/Total_order#Strict_total_order>`_.
///
/// A type conforming to `Comparable` need only supply the `<` and
/// `==` operators; default implementations of `<=`, `>`, `>=`, and
/// `!=` are supplied by the standard library::
///
///   struct Singular : Comparable {}
///   func ==(x: Singular, y: Singular) -> Bool { return true }
///   func <(x: Singular, y: Singular) -> Bool { return false }
///
/// **Axioms**, in addition to those of `Equatable`:
///
/// - `x == y` implies `x <= y`, `x >= y`, `!(x < y)`, and `!(x > y)`
/// - `x < y` implies `x <= y` and `y > x`
/// - `x > y` implies `x >= y` and `y < x`
/// - `x <= y` implies `y >= x`
/// - `x >= y` implies `y <= x`
protocol Comparable : _Comparable, Equatable {
    func <=(lhs: Self, rhs: Self) -> Bool
    func >=(lhs: Self, rhs: Self) -> Bool
    func >(lhs: Self, rhs: Self) -> Bool
}

We can see here that to make something comparable we need to implement 3 methods: <=, >= and >.

Here’s an example where we define a protocol called FastCar. Any class that conforms to this speed must implement a property called topSpeed. Lets write a Car class that we can initialize with a top speed.

protocol FastCar{
    var topSpeed:Int { get }
}

class Car: FastCar{
    var topSpeed:Int

    init(topSpeed:Int){
        self.topSpeed = topSpeed
    }
}
var subaruImpreza = Car(topSpeed: 130)
var mitsubishiColt = Car(topSpeed: 131)
var hondaCivic = Car(topSpeed: 139)

let cars = [subaruImpreza, mitsubishiColt, hondaCivic]

We’d like to be able to sort our cars by their top speed, and compare them by speed. We can add support for directly comparing cars using standard comparison operators by implementing the Comparable protocol as defined above. The comparable protocol specifies the following functions:

func <=(lhs: Car, rhs: Car) -> Bool
func >=(lhs: Car, rhs: Car) -> Bool
func >(lhs: Car, rhs: Car) -> Bool

However if we read the comments above the Comparable protocol definition we see that Comparable inherits from Equatable and that:

/// A type conforming to `Comparable` need only supply the `<` and
/// `==` operators; default implementations of `<=`, `>`, `>=`, and
/// `!=` are supplied by the standard library::

So to conform to this protocol we need to implement 2 functions:

func <(lhs: Car, rhs: Car) -> Bool
func ==(lhs: Car, rhs: Car) -> Bool

Once these two functions are implemented the Swift standard library provides default implementations of >, >=, <= and != which are implemented in terms of the 2 functions above (< and ==). So once we’ve implemented the 2 functions above for our Car type, we will have conformed to the protocol and we can use any of the build-in comparison operators with our Car type.

Note that we define these as global functions, not as methods on our Car class. Remember that function dispatch is based on the type-signature of the function arguments, so when <= gets called with 2 parameters of type Car, our custom function will get invoked. Here’s our Car implementation that conforms to the Comparable (and Equatable) protocol.

protocol FastCar{
    var topSpeed:Int { get }
}

func ==(lhs: Car, rhs: Car) -> Bool {
    return(lhs.topSpeed == rhs.topSpeed)
}

func <(lhs: Car, rhs: Car) -> Bool{
    return(lhs.topSpeed < rhs.topSpeed)
}

class Car: FastCar, Comparable{
    var topSpeed:Int

    init(topSpeed:Int){
        self.topSpeed = topSpeed
    }
}

var subaruImpreza = Car(topSpeed: 130)
var mitsubishiColt = Car(topSpeed: 131)
var hondaCivic = Car(topSpeed: 139)

hondaCivic > subaruImpreza    // true
hondaCivic <= mitsubishiColt  // false
  1. The @objc keyword is for Objective-C interoperability and it indicates that your class or protocol should be accessible to Objective-C code.