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 when f()
is defined inside a protocol extension.
Dynamic Dispatch
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.
Static Dispatch
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.
a.
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.
b.
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.
c.
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.
More
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"} }