Swift: dynamic dispatch with protocol and subclass

397 Views Asked by At

Please consider the following Swift 5 code:

protocol P: class {
    func call_foo()
    func foo()
    func call_bar()
    func bar()
}

extension P {
    func call_foo() { foo() }
    func foo() { print("P.foo") }
    func call_bar() { bar() }
    func bar() { print("P.bar") }
}

class C1: P {
    func foo() { print("C1.foo") }
}

class C2: C1 {
    func bar() { print("C2.bar") }
}

let c = C2()
c.call_foo()    //  C1.foo
c.foo()         //  C1.foo
c.call_bar()    //  P.bar
c.bar()         //  C2.bar

If the foo() call in P.call_foo() gets dynamically dispatched to C1.foo(), then why the bar() call in P.call_bar() does not get dynamically dispatched to C2.bar()?

The only difference is that foo() is overridden directly in the class that conforms to P, and bar() is only overridden in a subclass. Why does that make a difference?

Given that bar() is a protocol requirement, shouldn't all calls to it always get dynamically dispatched?

1

There are 1 best solutions below

3
On BEST ANSWER

In the context of your extension:

extension P {
    func call_foo() { foo() }
    func foo() { print("P.foo") }
    func call_bar() { bar() }
    func bar() { print("P.bar") }
}

C2 does not exist, P is a protocol, and methods are dispatched statically, and although bar() is a requirements of P, it is not implemented by C1 which has the conformance to P so:

let c1: some P = C1()
c1.call_foo()    //  C1.foo
c1.foo()         //  C1.foo
c1.call_bar()    //  P.bar
c1.bar()         //  P.bar

and that is normal, and interestingly you have:

let someP: some P = C2()
someP.call_foo()    //  C1.foo
someP.foo()         //  C1.foo
someP.call_bar()    //  P.bar
someP.bar()         //  P.bar

Meaning that if you only have a reference to some P, the subclass C2 of C1 behaves exactly as it's superclass: call_bar() calls P.bar() because C1 does not implement bar()

now let's look at what happens if you implement bar() in C1:

class C1: P {
    func foo() { print("C1.foo") }
    func bar() { print("C1.bar") }
}

class C2: C1 {
    override func bar() { print("C2.bar") }
}

If we use a reference to C1 using some P:

let c1: some P = C1()
c1.call_foo()    //  C1.foo
c1.foo()         //  C1.foo
c1.call_bar()    //  C1.bar
c1.bar()         //  C1.bar

now in call_bar() the compiler knows it has to use C1.bar() so with a reference to C2 using some P:

let someP: some P = C2()
someP.call_foo()    //  C1.foo
someP.foo()         //  C1.foo
someP.call_bar()    //  C2.bar
someP.bar()         //  C2.bar

The subclass C2 still behaves the same way as it's superclass C1 and it's implementation of bar() get's called. (And I find it somewhat reassuring when sublasses behave as their parent).

now let's check the original snippet :

let c = C2()
c.call_foo()    //  C1.foo
c.foo()         //  C1.foo
c.call_bar()    //  C2.bar
c.bar()         //  C2.bar

it work's !