What an interface is
An interface is a contract: a list of things a type promises to do, with no say in how. A Truck, a Robot and a Drone are different kinds of thing, but all of them can move. A "can move" contract lets code that only needs movement work with all three.
Where inheritance models "is a" (a Dog is an Animal), an interface models "can do". Swift's name for an interface is a protocol.
Declaring and adopting a protocol
A protocol lists requirements and gives no bodies:
protocol Speaker {
func speak()
}
A type adopts the protocol by naming it after a colon, and then implements every requirement:
class Dog: Speaker {
func speak() {
print("Woof")
}
}
Two points set this apart from inheritance. The implementation needs no override, because there is no parent body to replace. And the check is made by the compiler. If Dog adopts Speaker and has no speak(), the code does not compile. The error reads type 'Dog' does not conform to protocol 'Speaker', with a note that the protocol requires speak().
Using the protocol as a type
A protocol name can be the type of a variable, a parameter or an array element. The code then knows only what the protocol promises:
let rex: Speaker = Dog()
rex.speak()
This is polymorphism through a contract: the call reaches whichever type is behind rex. It lets unrelated types share one array:
struct Robot: Speaker {
func speak() { print("Beep") }
}
let things: [Speaker] = [Dog(), Robot()]
for t in things { t.speak() }
Woof
Beep
Dog is a class and Robot is a struct, and they have no common parent. The protocol is the only thing they share.
A protocol cannot be created on its own. Speaker() is a compile error, since a protocol has no implementation to build.
Many protocols, one parent
A class has at most one superclass but can adopt any number of protocols. List them after the colon, separated by commas. If the class has a superclass, the superclass goes first. Here Mover is a protocol with a single move() requirement:
class Puppy: Dog, Mover {
func move() { print("bounding") }
}
This is why protocols suit capabilities that cut across hierarchies, such as comparing or hashing. The standard library's Comparable and Hashable are protocols.
Default implementations
An extension on a protocol can supply a body for a requirement, and types that adopt the protocol then get it for free:
protocol Mover {
func move()
}
extension Mover {
func move() { print("moving") }
}
struct Robot: Mover {
func move() { print("rolling") }
}
A type that adopts Mover without writing move() prints moving. Robot writes its own and prints rolling. Because move() is a requirement of the protocol, a call through a Mover variable uses the type's own version. A method that exists only in the extension and is not a requirement is chosen from the variable's declared type, so a type's own method with the same name is ignored in that case.
Interface or abstract class
| Class with a base | Protocol | |
|---|---|---|
| Stored properties | Yes | No, only property requirements |
| Method bodies | Yes | Only through extensions |
| Who can adopt it | Subclasses of a class | Classes, structs and enums |
| How many | One superclass | Any number |
| Models | "is a" | "can do" |
The lesson's exercise
The starter code declares the Speaker protocol and a Solution class that builds a Dog and calls speak(). The Dog class is missing, and the output must be Woof.
- Declare
Dogand adoptSpeaker. Writeclass Dog: Speakerbelow the protocol. The colon is the same one used for a superclass, and no superclass is involved here. - Implement
speak. Addfunc speak()that printsWoof. A protocol requirement without a body in the type makes the class fail to compile, and nooverridekeyword is used.
protocol Speaker {
func speak()
}
class Dog: Speaker {
func speak() {
print("Woof")
}
}
class Solution {
func mainFunction() {
let rex: Speaker = Dog()
rex.speak()
}
}
The method name and signature must match the requirement exactly. A func speak(loudly: Bool) would be a different method and would not satisfy it.
Key takeaways
- A Swift interface is a
protocol: a list of requirements with no stored data. - Adopt one with
: ProtocolNameand implement every requirement, withoutoverride. - The compiler rejects a type that leaves a requirement out.
- A type can adopt many protocols, with the superclass first if there is one.
- Protocols work for structs and enums as well as classes.
