Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Protocol Extensions and subclasses

I am wondering why the following doesn't print out what I think it should.

/* Fails */
protocol TheProtocol {
    func update()
}

class A: TheProtocol {
}

class B : A {}

extension TheProtocol {
    func update() {
        print("Called update from TheProtocol")
    }
}

extension TheProtocol where Self: B {
    func update() {
        print("Called update from B")
    }
}

let instanceB = B()
instanceB.update()

let instanceBViaProtocol:TheProtocol = B()
instanceBViaProtocol.update()

This will print the following:

Called update from B
Called update from TheProtocol // Why not: Called update from B (extension)

I am especially wondering why

instanceBViaProtocol.update()

Doesn't execute the update() in the extension on TheProtocol:

extension TheProtocol where Self: B {
    func update() {
        print("Called update from B")
    }
}

I would think it would since B inherits from A which adopts TheProtocol, so I would think that B would then implicitly adopt TheProtocol as well. Moving the protocol adoption to B from A yields the expected result.

protocol TheProtocol {
    func update()
}

class A { // Remove TheProtocol
}

class B : A, TheProtocol {} // Add TheProtocol

extension TheProtocol {
    func update() {
        print("Called update from TheProtocol")
    }
}

extension TheProtocol where Self: B {
    func update() {
        print("Called update from B")
    }
}

let instanceB = B()
instanceB.update()

let instanceBViaProtocol:TheProtocol = B()
instanceBViaProtocol.update()

Result:

Called update from B
Called update from B

I took a look at https://medium.com/ios-os-x-development/swift-protocol-extension-method-dispatch-6a6bf270ba94#.6cm4oqaq1 and http://krakendev.io/blog/subclassing-can-suck-and-heres-why, but I was unable to figure this out. Are extension methods not honored on subclasses of entities that adopt the protocol?

like image 577
user6902806 Avatar asked Nov 08 '22 06:11

user6902806


1 Answers

The answer is Method Dispatch + a Bug in Swift.

Method dispatch is the mechanism used by the compiler to choose an implementation to execute when a method is invoked. Swift uses 3 kinds of method dispatch. You can read about it here

The dispatch method, is determined by the type of the reference, not by the type of the instance. In your case the reference type is TheProtocol.

let instanceBViaProtocol:TheProtocol = B()
instanceBViaProtocol.update()

You have a protocol extension that defines common behavior for a requirement method. In that case, dynamic dispatch is used. That means that the implementation declared in B should be used. But there is a bug in Swift that causes the issue.

For each type Swift uses a witness table to register the implementations used for dynamic dispatch. The bug causes the B class to fail to register its implementation of update() in the Witness table of TheProtocol. And when update is dispatched through the TheProtocol table, the wrong implementation is used.

Here you have your example with some changes. Notice that if you declare update in the superclass and override it in the subclass, it works as expected. That's the clearest way to see the bug to mee.

protocol TheProtocol {
    func update()
}

class A: TheProtocol {
    func update(){
        print("Implementation of A")
    }
}

class B : A {
    override func update(){
        print("Implementation of B")
    }
}

//All those who conform to TheProtocol will execute this.
extension TheProtocol {
    func update() {
        print("Common: TheProtocol")
    }
}
extension TheProtocol where Self: B {
    func update() {
        print("Common: TheProtocol for B's")
    }
}
extension TheProtocol where Self: A {
    func update() {
        print("Common: TheProtocol for A's")
    }
}


let instanceBViaProtocol:TheProtocol = B() //It prints "Implementation of B"
instanceBViaProtocol.update()

I hope this answers you question.

https://www.raizlabs.com/dev/2016/12/swift-method-dispatch/ has an awesome explanation on method dispatch in swift.

Here you can read a short thing I wrote about method dispatch in protocol extensions.

like image 90
Lio Avatar answered Nov 14 '22 21:11

Lio