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?
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 !