Consider following playground:
protocol A {
func f() -> String
}
extension A {
func f() -> String { return "AAAA" }
}
class B: A {}
class C: B {
func f() -> String { return "CCCC" }
}
let a: A = C()
let b: B = C()
let c: C = C()
a.f() // "AAAA" - why?
b.f() // "AAAA" - why?
c.f() // "CCCC"
I don't get why a.f()
and b.f()
return "AAAA"
- they are supposed to return "CCCC"
because func f() -> String
should be dynamically dispatched (as it declared in protocol).
If I change class B
to look like this:
class B: A {
func f() -> String { return "BBBB" }
}
then all three calls to .f()
return "CCCC"
as expected.
I feel like it is a bug in Swift compiler, I checked in Xcode 7.3.1 and 8.0-beta3, this behavior is reproducible in both.
Is this actually an expected behavior?
Thee are several rules involved here.
Sometimes Static Dispatch is used (in this case we must look at the type of the var/let to find out the implementation that will be used).
Other times Dynamic Dispatch is used instead (this means the implementation of the object inside the variable is used).
Let's consider the general example
let foo: SomeType1 = SomeType2()
foo.f()
I'll use the following definitions
classic implementation of f()
to indicate when f() is defined outside of a protocol extension (so inside a struct/class).
default implementation of f()
to indicate whenf()
is defined inside a protocol extension.
If SomeType1
is a struct/class
with it's own "classic" implementation of f()
then polymorphism is applied.
It means that if SomeType2
doesn't have a classic implementation of f()
then SomeType1.f()
is used. Otherwise SomeType2.f()
wins.
If SomeType1
doesn't have a classic implementation of f()
but has a default implementation, then polymorphism is turned off.
In this case default implementation of the type of the let/var
wins.
Let's look at your first example
let a: A = C()
a.f() // "AAAA"
In this A doesn't have it's own classic implementation (because it's not a struct/class) but has a default implementation. So polymorphism is turned off and A.f()
is used.
Same rule for your second example
let b: B = C()
b.f() // "AAAA"
B
doesn't have classic implementation of f(), but has a default implementation of f()
. So polymorphism is turned off and B.f()
(from the protocol extension) is used.
Finally the object of type C
is inside a constant of type C
.
var c:C
c.f() // "CCCC"
Here C
has a classic implementation of f()
. In this case the protocol implementation is ignored and C.f()
is used.
Let's see another example
protocol Alpha { }
extension Alpha { func f() -> String { return "Alpha"} }
protocol Beta { }
extension Beta { func f() -> String { return "Beta"} }
class Foo: Alpha, Beta { }
let alpha: Alpha = Foo()
alpha.f() // "Alpha"
let beta: Beta = Foo()
beta.f() // "Beta"
As you can see, again, the type of the constant containing the value wins. And if you put the Foo
object inside a Foo
constant you get a compile error
let foo: Foo = Foo()
foo.f() //
error: ambiguous use of 'f()'
foo.f()
^
Swift 2.playground:2:23: note: found this candidate
extension Beta { func f() -> String { return "Beta"} }
^
Swift 2.playground:6:24: note: found this candidate
extension Alpha { func f() -> String { return "Alpha"} }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With