Reputation: 12446
I can use generic protocols just fine, but am trying to grasp the reasoning behind some limitations of generic protocols.
Example:
internal protocol ArchEnemyable {
var archEnemy: Self? {
get
}
}
internal final class Humanoid: ArchEnemyable {
internal var archEnemy: Humanoid? // is the adoption to Self, works only when class is final
internal init(otherOne pOtherOne: Humanoid?) {
self.archEnemy = pOtherOne
}
}
I do not find any example where it would not work, when it is not final. Do one of you know an example, why you need to make the class final?
Without the protocol the code works fine:
internal class Humanoid {
internal var archEnemy: Humanoid? // Do not need to be final, without protocol
internal init(otherOne pOtherOne: Humanoid?) {
self.archEnemy = pOtherOne
}
}
internal class Human: Humanoid {
override internal var archEnemy: Humanoid? {
get {
return self
}
set {
}
}
}
internal class Ape: Humanoid {
}
internal let human: Humanoid = Human(otherOne: nil)
internal let ape: Humanoid = Ape(otherOne: nil)
human.archEnemy = ape
My first thought was that it is like this, because subclasses can not fulfill the promises of the protocol, since "Self" can not be concrete type of the subclass.
Overriding properties can NOT change the type. So it would not be possible for a "Human" to guarantee a "Human" archEnemy, which would violate the protocol.
BUT, exactly that is the case for methods!
So methods do not violate the protocol, even though it seemed for me like it at first sight.
A method declaration in a protocol which uses Self as parameter type, does not limit the adopting class to be final.
You have to replace Self with the concrete type like with properties using Self.
In methods you can subclass and the subclass does not replace Self with their concrete type, it stays the superclass type.
It is possible adding another method with a different signature, but it is not the one from the protocol, because Self
is only referring to the superclass' concrete type.
Example with methods:
internal protocol MythicalCreature {
func makeLoveWith(otherThing: Self) -> Void
}
internal class Kraken: MythicalCreature {
internal func makeLoveWith(otherThing: Kraken) -> Void {
print("Making love with other kraken")
}
}
internal class FlyingKraken: Kraken {
var canFly: Bool = true
// It is possible, but no obligation to add polymorphism, Self will not automatically be replaced by the subclass' concrete type
internal func makeLoveWith(otherThing: FlyingKraken) -> Void {
print("Making love with other kraken in the air")
}
}
let krakenWithDynamicFlyingKraken: Kraken = FlyingKraken()
let flyKraken: FlyingKraken = FlyingKraken()
krakenWithDynamicFlyingKraken(flyKraken) // USES the method of Kraken, no dynamic dispatch
internal class Hydra: MythicalCreature {
internal func makeLoveWith(otherThing: Hydra) -> Void {
print("Making love with other dragon")
}
}
internal class FlyingHydra: Hydra {
}
internal class BurrowingHydra: Hydra {
}
let hydra1: Hydra = FlyingHydra()
let hydra2: Hydra = BurrowingHydra()
hydra1.makeLoveWith(hydra2) // Self is not the concrete type of the subclasses
Then the promise of the protocol with the method is that Self is only the concrete type of the class adopting it, but not its subclasses.
So the question stays:
Why is Apple limiting classes that adopt protocols with properties with Self as type to be final, when they are not doing the same to methods?
Upvotes: 1
Views: 89
Reputation: 80781
I suspect the problem you've encountered stems from the fact that read-write properties are invariant in Swift. If you consider a more basic example without Self
:
class A {}
class B : A {}
class Foo {
var a = A()
}
class Bar : Foo {
// error: Cannot override mutable property 'a' of type 'A' with covariant type 'B'
override var a : B {
get {
return B() // can return B (as B conforms to A)
}
set {} // can only accept B (but not A, therefore illegal!)
}
}
We cannot override the read-write property of type A
with a property of type B
. This is because although reading is covariant, writing is contravariant. In other words, we can always make the getter return a more specific type from a given property as we override it – as that specific type will always conform/inherit from the base type of the property. However we cannot make the setter more type restrictive, as we're now preventing given types from being set that our original property declaration allowed to be set.
Therefore we reach this state where we cannot make the property any more or any less type specific as we override it, thus making it invariant.
This comes into play with a Self
typed property requirement, as Self
refers to the runtime type of whatever is implementing the protocol, therefore its static type will be the same type as the class declaration. Therefore when you come to subclass a given class with this protocol requirement, the static type of Self
in that subclass is the type of the subclass – thus the property would need to be covariant in order to meet this requirement, which it isn't.
You may then be wondering why the compiler doesn't allow you to satisfy this protocol requirement with a read-only computed property, which is covariant. I suspect this is due to the fact that a read-only computed property can be overridden by a settable computed property, thus creating invariance. Although I'm not entirely sure why the compiler can't pick that error up at the point of overriding, rather than preventing read-only properties entirely.
In any case, you can achieve the same result through using a method instead:
protocol Bar {
func foo() -> Self
}
class Foo : Bar {
required init() {}
func foo() -> Self {
return self.dynamicType.init()
}
}
If you need to implement a property from a protocol with a Self
requirement, then you will indeed have to make the class final
in order to prevent subclassing and therefore violations of the protocol requirement for read-write properties.
This reason that this all works fine with method inputs is due to overloading. When you implement a method with a Self
input type in a subclass, you're not overriding the one in the superclass – you're simply adding another method that can be called. When you come to call the method, Swift will favour the one with the more type specific signature (which you can see in your example).
(Note if you try and use the override
keyword on your method, you'll get a compiler error.)
Also as you've discovered for when Self
refers to a method input, you don't even have to implement a new method in your subclass to take account of the change in Self
's type. This is due to the contravariance of method inputs, which I explain further in this answer. Basically the superclass method already satisfies the subclass requirement, as you can freely replace the type of a given method input with its supertype.
Upvotes: 3