0

I'm wondering is there a way to work with protocol default implementations in polymorphic style. Example

protocol RockInterface {
}

extension RockInterface {
    func foo() {
        print("You Rock")
    }
}

extension RockInterface where Self: Metal {
    func foo() {
        print("Metal")
    }
}

extension RockInterface where Self: Grunge {
    func foo() {
        print("Grunge")
    }
}

class Rock: RockInterface {
    init() {
        foo()
    }
}

class Metal: Rock {

}

class Grunge: Rock {

}

let rock = Rock()       //prints "You Rock"
let metal = Metal()     //prints "You Rock"
let grunge = Grunge()   //prints "You Rock"

I expected Metal() to print "Metal", and Grunge to print "Grunge". But it seems that default implementations are solved at compile time instead of runtime. Is my assumption right of wrong? How could I get expected behavior?

ChrisF
  • 134,786
  • 31
  • 255
  • 325
AV8R
  • 63
  • 1
  • 7
  • Interesting. I am not 100% on whether this is expected behavior, but the results you're seeing stems from `foo()` being called inside of `init`. If you do something like `Metal().foo()` it prints "You Rock" then the appropriate override on the post-initialized call of `foo()`, in this case "PIFL". – Andrew Robinson Apr 29 '16 at 19:43
  • Really `metal.foo()` prints "Metal". – AV8R Apr 29 '16 at 19:54
  • But actually original issue for me was with `viewDidAppear()`. I had some code similar to this and `foo()` was called in `viewDidAppear()`. Even from there `foo()` prints "You Rock". – AV8R Apr 29 '16 at 19:58
  • `metal.foo()` would print "Metal", I was talking about `Metal().foo()` (prints in `init` and `foo`). So, you had a custom `UIViewController` with a similar setup, and `viewDidAppear()` didn't call the appropriate `foo()`? – Andrew Robinson Apr 29 '16 at 20:01
  • Yes. Its call base `foo()` instead of concreate – AV8R Apr 29 '16 at 20:30

1 Answers1

0

The are at least two factors that contribute to the behaviour you see, some within your control, and some not.

  1. functions that are not part of the protocol requirements are statically dispatched. If you want dynamic dispatch, then you'll need to add the method to the protocol declaration:
protocol RockInterface {
    func foo()
}
  1. however, the above won't solve your problem, since subclasses inherit the protocol witness table of the parent class. See this excellent answer for more details about this.

I'd also argue you design is a good one, since you tightly coupled the protocol and the classes that conform to that protocol. If you really need the behaviour you described, then one solution would be to drop the protocol extensions, and implement the foo method within each class:

protocol RockInterface {
    func foo()
}

class Rock: RockInterface {
    init() {
        foo()
    }
    
    func foo() {
        print("You Rock")
    }
}

class Metal: Rock {
    override func foo() {
        print("Metal")
    }
}

class Grunge: Rock {
    override func foo() {
        print("Grunge")
    }
}

let rock = Rock()       //prints "You Rock"
let metal = Metal()     //prints "Metal"
let grunge = Grunge()   //prints "Grunge"
Cristik
  • 30,989
  • 25
  • 91
  • 127