Extending the protocol using the method implementation puts us in a situation where sometimes we are polymorphic (the internal type of an object is what matters) and sometimes not (the way that an object is externally typed or cast, matters).
To study this, I use a test grid covering the following parameters:
Is the protocol and method necessary?
Is the adoptive parent a structure or class?
Does the adopter use the method?
1. The protocol does not require a method
Let's start with the answer to the first question: NO. So here are the type declarations:
protocol Flier { } extension Flier { func fly() { print("flap flap flap") } } struct Bird : Flier { } struct Insect : Flier { func fly() { print("whirr") } } class Rocket : Flier { func fly() { print("zoooom") } } class AtlasRocket : Rocket { override func fly() { print("ZOOOOOM") } } class Daedalus : Flier {
And here are the tests:
let b = Bird() b.fly() // flap flap flap (b as Flier).fly() // flap flap flap let i = Insect() i.fly() // whirr (i as Flier).fly() // flap flap flap let r = Rocket() r.fly() // zoooom (r as Flier).fly() // flap flap flap let r2 = AtlasRocket() r2.fly() // ZOOOOOM (r2 as Rocket).fly() // ZOOOOOM (r2 as Flier).fly() // flap flap flap let d = Daedalus() d.fly() // flap flap flap (d as Flier).fly() // flap flap flap let d2 = Icarus() d2.fly() // fall into the sea (d2 as Daedalus).fly() // flap flap flap (d2 as Flier).fly() // flap flap flap
Result: What matters is how the object is typed. In fact, the compiler only knows how the object is typed, where to look for the fly implementation that will be called; all necessary information is present at compilation time. In general, there is no need for dynamic dispatch.
The exception is AtlasRocket, a subclass whose superclass has its own implementation. When AtlasRocket is introduced as its superclass, Rocket, it is still (for flight purposes) AtlasRocket. But this is not surprising, because it is a subclass / superclass situation where polymorphism and dynamic dispatch act; Obviously, we are not going to disable dynamic sending just because there is also a protocol extension in the history.
2. The protocol requires a method
Now the answer to the first question is YES. Type declarations are exactly the same as before, except that I added "2" to the names of all types, and the protocol itself contains this method as a requirement:
protocol Flier2 { func fly() // * } extension Flier2 { func fly() { print("flap flap flap") } } struct Bird2 : Flier2 { } struct Insect2 : Flier2 { func fly() { print("whirr") } } class Rocket2 : Flier2 { func fly() { print("zoooom") } } class AtlasRocket2 : Rocket2 { override func fly() { print("ZOOOOOM") } } class Daedalus2 : Flier2 { // nothing } class Icarus2 : Daedalus2 { func fly() { print("fall into the sea") } }
And here are the tests; they are the same tests, and "2" is added to the names of all types:
let b = Bird2() b.fly() // flap flap flap let i = Insect2() i.fly() // whirr (i as Flier2).fly() // whirr (!!!) let r = Rocket2() r.fly() // zoooom (r as Flier2).fly() // zoooom (!!!) let r2 = AtlasRocket2() r2.fly() // ZOOOOOM (r2 as Rocket2).fly() // ZOOOOOM (r2 as Flier2).fly() // ZOOOOOM (!!!) let d = Daedalus2() d.fly() // flap flap flap (d as Flier2).fly() // flap flap flap let d2 = Icarus2() d2.fly() // fall into the sea (d2 as Daedalus2).fly() // flap flap flap (d2 as Flier2).fly() // flap flap flap
Result: Polymorphism came to life: what really is an object. You can call Insect2 Flier2, but it still flies like Insect2. You can call Rocket2 Flier2, but it still flies like Rocket2. You can name AtlasRocket2 Flier2, but it still flies like AtlasRocket2.
The exceptional case here is the one that is indicated by your question, namely, when the adopter himself does not implement the method. Thus, we call Icarus2 a Daedalus2, and here it is, it flies like Daedalus2, just like in the previous example. There was no need to include polymorphism, and the compiler knew this from the very beginning, because the Icarus2 implementation is not an override .