I have a protocol with PAT that provides a default implementation in an extension. Then, a class conforming that protocol provides an "override". This override is not called, and the compiler "prefers" the default.
//---- Definition of the protocol with a default implentation, because I am forced to.
protocol Bag: AnyObject {
associatedtype BagObject
func add(_ e: BagObject)
}
extension Bag where BagObject: Equatable {
// I here give a default implementation, because I can't do differently, as this
// is an extension, and it is an extension because it only applies to assoicated
// types that are Equatables
func contains(_ e: BagObject) -> Bool {
print("Default implementation is called")
return false
}
}
///---- Definition of a class that gives a concrete implementation of the protocol
class BagConcreteImplementation<BagObject>: Bag {
var bag = Array<BagObject>()
func add(_ e: BagObject) { bag.append(e) }
}
extension BagConcreteImplementation where BagObject: Equatable {
// I here give a concrete implementation when the generic type is Equatable
func contains(_ e: BagObject) -> Bool {
print("Concrete implementation is called")
return bag.contains(e)
}
}
///---- This is a class that encapsulate a bag, in real life, this class is adding some filtering to objects that can be added to the bag
class AClassThatHaveABag<BagType: Bag> {
typealias BagObject = BagType.BagObject
let myBag: BagType
init(bag: BagType) { myBag = bag }
}
extension AClassThatHaveABag where BagType.BagObject: Equatable {
func contains(_ e: BagObject) {
// The problem here is that the compiler only knows that myBag is a Bag
// of Int and therefore calls the default implementation
// It does not call the concrete implementation of the concrete class that WILL be provided
myBag.contains(e)
}
}
let aBagOfInt = BagConcreteImplementation<Int>()
let objectThatContainsABagOfInt = AClassThatHaveABag(bag: aBagOfInt)
aBagOfInt.contains(0)
objectThatContainsABagOfInt.contains(0)
// Prints
// Concrete implementation is called
// Default implementation is called
We can clearly see the direct call is call the concrete implementation, while the encapsulated call is calling the default implementation.
How to do to make sure the concrete implementation is always called, even through an encapsulation ?
CodePudding user response:
I tried a few things, and somehow this works:
Declare contains
in the protocol, not in an extension, like this:
func contains<T>(_ e: T) -> Bool where T: Equatable, T == BagObject
If you do things like T == BagObject
in a class, a compiler error will show up saying that this makes T
redundant, but apparently this is okay in a protocol.
Then, implement the default implementation like this:
extension Bag {
func contains<T>(_ e: T) -> Bool where T: Equatable, T == BagObject {
print("Default implementation is called")
return false
}
}
Then, you can just implement the concrete implementation directly in the class, as a non-generic method.
class BagConcreteImplementation<BagObject> : Bag {
func contains(_ e: BagObject) -> Bool where BagObject : Equatable {
print("Concrete implementation is called")
return bag.contains(e)
}
var bag = Array<BagObject>()
func add(_ e: BagObject) { bag.append(e) }
}
Without changing the call site, the code would print:
Concrete implementation is called
Concrete implementation is called
CodePudding user response:
For sake of completeness, I post here another solution, which avoids the use of T == BagObject
which I find (my humble opinion) not that clean.
The idea is to use a specialized version of Bag
, called BagOfEquatables
, and extend the classes
protocol Bag: AnyObject {
associatedtype BagObject
func add(_ e: BagObject)
}
// Specialized version of Bag for Equatables
protocol BagOfEquatables: Bag where BagObject: Equatable{
func contains(_ e: BagObject) -> Bool
}
extension BagOfEquatables {
func contains(_ e: BagObject) -> Bool {
print("Default implementation is called")
return false
}
}
///---- Definition of a class that gives a concrete implementation of the protocol
class BagConcreteImplementation<BagObject>: Bag {
var bag = Array<BagObject>()
func add(_ e: BagObject) { bag.append(e) }
}
extension BagConcreteImplementation: BagOfEquatables where BagObject: Equatable {
func contains(_ e: BagObject) -> Bool {
print("Concrete implementation is called")
return bag.contains(e)
}
}
///---- This is a class that encapsulate a bag
class AClassThatHaveABag<BagType: Bag> {
typealias BagObject = BagType.BagObject
let myBag: BagType
init(bag: BagType) { myBag = bag }
}
extension AClassThatHaveABag where BagType: BagOfEquatables {
func contains(_ e: BagObject) -> Bool {
myBag.contains(e)
}
}
let aBagOfInt = BagConcreteImplementation<Int>()
let objectThatContainsABagOfInt = AClassThatHaveABag(bag: aBagOfInt)
aBagOfInt.contains(0)
objectThatContainsABagOfInt.contains(0)
// Prints
// Concrete implementation is called
// Concrete implementation is called
CodePudding user response:
You said:
This override is not called, and the compiler "prefers" the default.
The issue is that you have supplied the default implementation of the method in the extension, but never declared it as part of the protocol, itself. As a result, the compiler will use “static dispatch”.
If you include the method in the protocol, the compiler will use “dynamic dispatch” to resolve which method to call.
For a discussion of static vs. dynamic dispatch see WWDC 2016 Understanding Swift Performance.
For example:
protocol Foo {
// without this declaration, this will use static dispatch;
// uncomment the following line for dynamic dispatch
// func foo()
}
extension Foo {
func foo() {
print("default")
}
}
struct Bar: Foo {
func foo() {
print("bar implementation")
}
}
let bar: Foo = Bar()
bar.foo() // “default” with static dispatch; ”bar implementation“ with dynamic dispatch