Setting up a method in a subclass that is not called

It seems that a function defined as a tuning point in a protocol with a default implementation in the protocol extension cannot be configured in a subclass that inherits the protocol indirectly through the base class unless that base class has configured the function first.

Here is a simple protocol:

protocol MyProtocol { func myFunc() -> String } 

With default implementation:

 extension MyProtocol { func myFunc() -> String { return "hello from extension" } } 

Create a base class and subclass as follows:

 class BaseClass: MyProtocol { } class SubClass: BaseClass { func myFunc() -> String { return "hello from SubClass" } } BaseClass().myFunc() // "hello from extension" (BaseClass() as MyProtocol).myFunc() // "hello from extension" SubClass().myFunc() // "hello from SubClass" (SubClass() as BaseClass).myFunc() // "hello from extension" (SubClass() as MyProtocol).myFunc() // "hello from extension" 

Now with the setup in the base class:

 class BaseClass: MyProtocol { func myFunc() -> String { return "hello from BaseClass" } } class SubClass: BaseClass { override func myFunc() -> String { return "hello from SubClass" } } BaseClass().myFunc() // "hello from BaseClass" (BaseClass() as MyProtocol).myFunc() // "hello from BaseClass" SubClass().myFunc() // "hello from SubClass" (SubClass() as BaseClass).myFunc() // "hello from SubClass" (SubClass() as MyProtocol).myFunc() // "hello from SubClass" 

Is this expected behavior?

Change 14-new:

Note about this article: http://nomothetis.svbtle.com/the-ghost-of-swift-bugs-future of matt comment:

I think this is not related to my question, as the article does not cover the case when a subclass inherits the protocol indirectly (which, apparently, matters). In this later case, it does not seem obvious to me that static dispatch occurs even if the function is a configuration point (part of the protocol requirements). Depending on the type selected, the behavior is different during a conversation.

The article considers another case when a function has a default implementation in an extension, not being part of the protocol requirements, and can change the settings of a subclass.

Edit 17 new:

Perhaps a duplicate in Swift, why the subclass method cannot override the one provided by the protocol extension in the superclass

+1
source share
1 answer

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 { // nothing } class Icarus : Daedalus { func fly() { print("fall into the sea") } } 

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 .

+2
source

All Articles