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
- The
@objc
keyword is for Objective-C interoperability and it indicates that your class or protocol should be accessible to Objective-C code. ↑