What a protocol is
A protocol describes a capability that many unrelated types can share. It is a contract: it lists what a conforming type must provide, and it writes no method bodies.
protocol Describable {
func describe() -> String
}
Any type can sign the contract by putting : Describable after its name and implementing every requirement:
struct Dog: Describable {
func describe() -> String { return "Dog named Rex" }
}
Structs, classes and enums can all conform. Two types that have nothing else in common, such as a Dog and a City, can both conform to the same protocol, and code that only needs the capability can treat them alike.
Using a protocol as a type
Once types conform, the protocol can be used as a type in its own right. An array of [any Describable] can hold a Dog and a City side by side. The word any marks the element type as "some value whose type conforms to Describable", and the concrete type may differ from element to element.
When a loop calls item.describe(), Swift runs the implementation belonging to the actual type of that element. One interface gives many behaviors. This is called polymorphism.
Step by step
- Declare the protocol.
protocol Describable { }is written like a struct, with the keywordprotocol. - Add the requirement. Inside it, write the signature only:
func describe() -> String. There are no braces and no body. - Create
Dog.struct Dog: Describable { }promises to follow the contract. The compiler now checks that promise. - Implement the method in
Dog. It must have the same name, parameters and return type, and this time it has a body:func describe() -> String { return "Dog named Rex" }. - Create
Citythe same way, returning"City of Paris". It is a separate type with no shared ancestor, so the protocol is the only thing connecting it toDog. - Make the array.
let items: [any Describable] = [Dog(), City()]. The type annotation is what allows two different types in one array. - Loop and print.
for item in items { print(item.describe()) }. The loop only needs to know that everyitemcan describe itself.
The solution in Swift
protocol Describable {
func describe() -> String
}
struct Dog: Describable {
func describe() -> String { return "Dog named Rex" }
}
struct City: Describable {
func describe() -> String { return "City of Paris" }
}
let items: [any Describable] = [Dog(), City()]
for item in items {
print(item.describe())
}
The output is:
Dog named Rex
City of Paris
The contract is enforced
If a type claims to conform but misses a requirement, or implements it with a different signature, the program doesn't compile. For example, a Dog whose describe() returns Int produces:
error: type 'Dog' does not conform to protocol 'Describable'
note: protocol requires function 'describe()' with type '() -> String'; add a stub for conformance
note: candidate has non-matching type '() -> Int'
Read it from the error to the notes: the error names the type that broke its promise, and the notes point at the missing or mismatched requirement. This strictness is the point. If the program compiles, every element of a [any Describable] is guaranteed to answer describe(). Likewise, putting a plain Int into that array is rejected, because Int doesn't conform.
More to know
- A protocol can be a parameter type.
func show(_ d: some Describable)accepts any single conforming value.somemeans one specific conforming type chosen by the caller, whileanyallows a mix of types at run time. - Classes conform too. A subclass that overrides the method, with
override, is picked when the object is a subclass instance, so an array holding a base object and a subclass object calls each one's own version. - One-line bodies. The braces are still required in
{ return "Dog named Rex" }. A single-expression body may leave outreturn. - Several protocols. A type can conform to many protocols, separated by commas:
struct Dog: Describable, Equatable.
Common mistakes
- Writing a body in the protocol. The requirement is only a signature.
- Forgetting
: Describable. ADogwith a perfectdescribe()still can't go into the array if it never declared conformance. - Mismatched signature. A different return type or parameter list doesn't satisfy the requirement.
- Forgetting the parentheses. The array holds instances, so write
Dog(), notDog.
Key takeaways
- A protocol lists requirements; conforming types provide the bodies.
- Unrelated types can share one capability through a protocol.
[any Describable]mixes conforming types, and each call runs the right implementation.- The compiler refuses any type that does not fully meet its contract.
