-2

So I was just playing with the protocols and just got confused with the following behaviour. I have a protocol with the function and a base class with the same function name. Now, another class is inheriting from a base class and implementing a protocol. But it is not producing error in some scenarios.

For example:

protocol P {
    func greetings()
}

class Base {
    func greetings() {
        print("Hello Base")
    }
}

class A: Base {

    override func greetings() {
        print("Hello Class")
    }
}

extension A: P {

}

let a = A()
print(a.greetings()) // print: Hello Class

let b:Base = A()
print(b.greetings()) // print: Hello Class

let p:P = A()
print(p.greetings()) // print: Hello Class

So, how is the conformance of the class to the protocol happening?

Also in the following case, when protocol function has default implementation, it compiles without any error and always opt from base class function.

protocol P {
    func greetings()
}
extension P {
    func greetings() {
       print("Hello Protocol")
    }
}
class Base {
    func greetings() {
        print("Hello Base")
    }
}

class A: Base {

}
extension A: P {

}

let a = A()
print(a.greetings()) // print: Hello Base

let b:Base = A()
print(b.greetings()) // print: Hello Base

let p:P = A()
print(p.greetings()) // print: Hello Base

Why it always opt for a base class function? And Why no compile time error because of ambiguous function call?

Paulw11
  • 108,386
  • 14
  • 159
  • 186
Hobbit
  • 601
  • 1
  • 9
  • 22

1 Answers1

1

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.

Paulw11
  • 108,386
  • 14
  • 159
  • 186