Which method runs through a protocol extension: when is a call dispatched dynamically and when statically?
Predict first
What does this print?
protocol Greeter { func hello() -> String }
extension Greeter {
func hello() -> String { "default hello" }
func bye() -> String { "default bye" }
}
struct En: Greeter {
func hello() -> String { "en hello" }
func bye() -> String { "en bye" }
}
let e = En()
let g: Greeter = En()
print(e.hello(), e.bye())
print(g.hello(), g.bye())
class Animal { func sound() -> String { "..." } }
extension Animal { func kind() -> String { "animal" } }
class Dog: Animal { override func sound() -> String { "woof" } }
let a: Animal = Dog()
print(a.sound(), a.kind())
protocol Shape { var name: String { get } }
extension Shape { var name: String { "shape" } }
struct Sq: Shape { var name: String { "square" } }
struct Ci: Shape {}
func printName<S: Shape>(_ s: S) { print(s.name) }
printName(Sq()); printName(Ci())
class Base: Greeter { }
class Derived: Base { func hello() -> String { "derived hello" } }
let b: Greeter = Derived()
print(b.hello(), Derived().hello())
func viaGeneric<T: Greeter>(_ t: T) -> String { t.hello() + "/" + t.bye() }
print(viaGeneric(En()), viaGeneric(g as! En))
- en hello en bye / en hello default bye / woof animal / square / shape / default hello derived hello / en hello/default bye en hello/default bye
- en hello en bye / en hello en bye / woof animal / square / shape / derived hello derived hello / en hello/en bye en hello/en bye
- en hello en bye / en hello default bye / woof animal / shape / shape / default hello derived hello / en hello/default bye en hello/default bye
Mental model. A company phone directory: requirements are listed in the official directory, extras are only in the sender's own address book.
A method that is a protocol requirement is dispatched through the witness table, so the conforming type's version wins even through the protocol type: g.hello() is "en hello" and the generic printName prints "square". A method that exists only in the extension (bye) is dispatched statically from the declared type: g.bye() on a Greeter gives "default bye" although En has its own, while e.bye() on the struct gives "en bye". Extension methods on a class (kind()) are not overridable either.
The last case: Base: Greeter uses the default hello; Derived.hello() is a new method, not an override, because the witness is recorded for Base. Through Greeter or a generic constraint you get "default hello"; only a direct Derived call gets "derived hello". Put every customisation point in the protocol itself (Q61, Q190).
Only protocol requirements are dispatched dynamically.
Common traps
- Adding a method only in an extension and expecting overrides to work. Add it to the protocol.
- Defining a method in a subclass of a conforming class and expecting it to satisfy the protocol. It does not.
They might follow up with
- How do you see it? Check the SIL witness tables (Q190).
Related questions
- 1.11.1 Static vs dynamic dispatch in Swift?
- 5.1.4 How do you read SIL to tell static dispatch from dynamic dispatch?
← In what order are operands, default arguments, autoclosures and… How does synthesised Decodable treat missing keys, nulls, default… →