drekka
drekka

Reputation: 21883

Swift: Why is the default protocol implementation used when an override is in place?

My intention in the original code I've distilled my example from was to have a protocol provide some default implementations for a number of abstract classes and to override as necessary in the hierarchies below those classes. However it didn't go as planned.

Here's the base code (taken from a playground where I reduced the original code to the essence of the problem):

// protocol and default impls.

protocol MyProtocol {
    func hello()
}

extension MyProtocol {
    func hello() { print("hello from default in protocol extension") }
}

// Class hierarchy

class AbstractParent: MyProtocol {}

class Child: AbstractParent {
    func hello() { print("hello from child") }
}

(Child() as MyProtocol).hello()
Child().hello()

Running this I expected to see:

hello from child
hello from child

But instead got:

hello from default in protocol extension
hello from child

It makes sense to me that because the first call is being made from a Child cast to a MyProtocol that it calls the protocol function even though the function exists in the Child because at compile time it only sees MYProtocol.

However ...

If I mode the abstract parent to include a default implementation like this:

// protocol and default impls.

protocol MyProtocol {
    func hello()
}

extension MyProtocol {
    func hello() { print("hello from default in protocol extension") }
}

// Class hierarchy

class AbstractParent: MyProtocol {
    func hello() { print("hello from abstract parent") }
}

class Child: AbstractParent {
    override func hello() { print("hello from child") }
}

(Child() as MyProtocol).hello()
Child().hello()

I now get the expected behaviour:

hello from child
hello from child

Now even though we are still casting to MyProtocol it's seeing the implementation in Child.

Can anyone explain why adding a implementation to the abstract parent class makes this work?

Upvotes: 2

Views: 369

Answers (1)

drekka
drekka

Reputation: 21883

Had a conversation with a colleague that knows Swift better than I and he said this is an example of a rather controversial known thing in Swift.

I'll try and outline it here so that others can understand as well.

As I understand it - When Swift is resolving the function it starts with the most common ancestor that implements it. So in my first example where it's executing the wrong one, it starts with AbstractParent and finds the hello() function in the protocol. Because AbstractParent does not have an implementation of that function and the hello() function in Child is not declared with override the Swift compiler then regards it as a different function even thought it looks like an override. Hence it doesn't call it.

In the second example we do have a hello() function in the AbstractParent and a real override in Child. So Swift sees the abstract's implementation and the override and calls the correct one.

The issue is effectively one of how Swift see's things when resolving and even though we as "average" developers think of any declared function in an implementation as overriding anything in a protocol it's actually not always the case.

Correctly the only known solution to this is to do as I did and add default implementations to the abstract classes.

For my project I'm going to have a think about my implementations and hierarchies to see if there's a better solution but so far it appears not.

Upvotes: 2

Related Questions