Say you have a protocol and an extension of it, like so:
protocol P {
var item: String { get }
}
extension P {
var item: String {
return "P"
}
}
Now imagine a class that conforms to that protocol and uses the property
declared there but doesn’t specialise it in any way:
class A: P {
var usedItem: String {
return item // A uses 'item' but doesn't override or specialise it in
any way
}
}
let a = A()
a.item // returns “P"
a.usedItem // returns “P"
Not surprisingly, both results equal “P” since A doesn’t specialise 'item'. Now
imagine a class B that subclasses A and *does* specialise 'item':
class B: A {
var item: String { // subclass specialises 'item' by "overriding" the
protocol extension implementation
return "B"
}
}
let b = B()
b.item // returns “B”
No surprise there either. B specialises 'item' so that’s what gets used. But...
b.usedItem // returns “P” !!?
I can hear you thinking "That's because 'usedItem' is being statically
dispatched, since it's not declared in the protocol" but adding a declaration
for 'usedItem' to the protocol doesn't help! I'll get to that in a moment.
Now consider class C, which is similar to B but also overrides A’s
implementation of 'usedItem' to do, well, exactly what A does (at least
syntactically):
class C: A {
var item: String {
return "C"
}
override var usedItem: String {
return item
}
}
let c = C()
c.item
c.usedItem
Now we get “C” for both calls, as desired, but having to add the override
keyword just for this is ugly at best. What, then, if we did add a declaration
for 'usedItem' to the protocol?
protocol P {
var item: String { get }
var usedItem: String { get }
}
extension P {
var item: String {
return "P"
}
var usedItem: String {
return item
}
}
class A: P {
}
let a = A()
a.item // returns “P"
a.usedItem // returns “P"
No surprises here.
class B: A {
var item: String {
return "B"
}
}
let b = B()
b.item // returns “B”
b.usedItem // still returns “P” !!
The result is still "P", even though 'usedItem' is now being dynamically
dispatched. Yes, B doesn't specialise the protocol extension's implementation
of 'usedItem' so there really isn't an implementation of that to dynamically
dispatch but =that shouldn't matter= (yet it does). B does specialise 'item',
though, so *that* should be dynamically dispatched (since it is declared in the
protocol). And it is, if called directly, but if it's called from within the
protocol extension's default implementation of another dynamically dispatched
invocation that isn't itself specialised, then it's not.
Of course, there's a solution similar to that of class C but, like that
solution, this one also involves the presence of extra code that is
syntactially identical to the default implementation.
class C: A {
var item: String {
return "C"
}
var usedItem: String {
return item
}
}
let c = C()
c.item // returns “C”
c.usedItem // now returns “C”, as desired.
What I find most surprising is that the static or dynamic dispatch behaviour of
a method in the protocol extension is not determined solely by the absence or
presence of its declaration in the protocol but also by whether or not
conforming types actually have a specialised implementation. As I pointed out
above, that second bit should not matter, yet it does. It seems to me that this
is a bug in how protocol extensions work but I might be wrong about that.
Any thoughts?
Thanks.
Wagner
_______________________________________________
swift-users mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-users