The type of the variable you assign an object to does not control how functions are dispatched on that object. When looking for a function, the computer starts at the object and walks "up" through the class hierarchy looking for an implementation. ie. It starts at A
and if it doesn't find a function, it goes up to Base
(A
's superclass) and looks there. If still not found it goes up to the next superclass (which there isn't one in this case). If the function isn't found then you get an error.
In your first example you have an instance of A
and A
overrides the base greetings
so that is that implementation that will always be used. This is related to an important concept in Object Oriented Programming - Substitutability.
In this case you can declare a variable of type Base
but assign an instance of a more specialised class - A
, but the program still operates correctly.
Remember that type inference as shown in your first assignment (let a = A()
) is not present in all languages. In many cases you need to explicitly declare a variable's type. If declaring a variable of type Base
meant that the Base
function was called instead of subclass, it would defeat the purpose of subclassing.
With regard to your question
how is the conformance of the class to the protocol happening?
A protocol is a specification but not an implementation (we will look at your question around default implementations next). Your protocol says that all things that conform to P
must provide a greeting
function. Clearly both Base
and A
provide a greeting function. Your extension merely adds the stipulation that A
conforms to P
. It doesn't need to add the implementation of P
because the greeting
method already exists.
If you removed the extension A: P
then let p:P = A()
would give an error because the compiler no longer knows that A
conforms to P
.
This brings us to your last two questions:
Why it always opt for a base class function?
And Why no compile time error because of ambiguous function call?
From The Swift Programming Language
You can use protocol extensions to provide a default implementation to any method or computed property requirement of that protocol. If a conforming type provides its own implementation of a required method or property, that implementation will be used instead of the one provided by the extension.
In your second example block, A
doesn't override the Base
implementation of greeting
, so the Base
implementation is always called.
The default implementation of greeting
that you added to P
will only be used in the case where a class that conforms to P
doesn't provide an implementation.
Default implementations can make it easier to conform to a protocol by supplying implementation where there is some behaviour that will apply to all or a majority of cases. Default implementations are always ignored if the conforming class provides an implementation
Consider the following example:
Protocol Vehicle {
func soundHorn()
var numberOfDoors: Int
}
extension Vehicle {
func soundHorn() {
print("Beep")
}
}
class SportsCar: Vehicle {
var numberOfDoors: Int {
return 2
}
}
class Sedan: Vehicle {
var numberOfDoors: Int {
return 4
}
}
class Truck: Vehicle {
var numberOfDoors: Int {
return 2
}
func soundHorn() {
print("**HONK**")
}
}
Here we have defined a simple protocol, Vehicle
, that says that a Vehicle
has a number of doors and can sound its horn. We provided a default soundHorn
that prints "Beep". We then declared classes that implement this protocol. The two different types of car have different numbers of doors but we are happy with the default horn. For a Truck
we have a bigger, louder horn, so we implement soundHorn
rather than using the default:
var v: Vehicle = SportsCar()
v.soundHorn() // Prints "beep"
v = Truck()
v.soundHorn() // Prints "**HONK**"
Note how v
is of type Vehicle
but we get the correct horn based on the object that is actually assigned to v
. The compiler is happy because it knows that all Vehicles
can soundHorn
; it doesn't need to know the specific vehicle type.