'A Swift protocol requirement that can only be satisfied by using a final class
I'm modeling a owner/ownee scheme on Swift:
class Owner<T: Ownee> {
// ...
}
protocol Ownee {
var owner: Owner<Self> { get }
}
Then I have a pair of classes professor/student that adhere to the modeled types above:
class Professor: Owner<Student> {
// ...
}
class Student: Ownee {
let professor: Professor
var owner: Owner<Student> { // error here (see below)
return professor
}
init(professor: Professor) {
self.professor = professor
}
}
However I get the following error on the definition of var owner
in the Student
class:
Protocol 'Ownee' requirement 'owner' cannot be satisfied by a non-final class ('Student') because it uses 'Self' in a non-parameter, non-result type position
I'm trying to understand what's the cause for this error, why making the class Student
final would fix it, and if there's some workaround to be able to model this differently, without making this class final. I've googled about that error, but haven't found much so far.
Solution 1:[1]
The Error is correct. You have to make your class final, since no subclasses could conform your protocol Ownee
.
Consider this subclass:
class FirstGradeStudent: Student {
// Student contains following variable:
// var owner: Owner<Student> {
// return professor
// }
}
As you can see, it would have to implement var owner: Owner<Student>
because of his parent, but it should be implementing var owner: Owner<FirstGradeStudent>
instead, because the protocol contains var owner: Owner<Self> { get }
and in this case Self
would be FirstGradeStudent
.
Workaround
1: Define a superclass to Ownee
, it should be used by Owner
:
class Owner<T: OwneeSuper> {
// ...
}
protocol OwneeSuper {}
protocol Ownee: OwneeSuper {
associatedtype T: OwneeSuper
var owner: Owner<T> { get }
}
OwneeSuper
is just a workaround to overcome this problem, otherwise we would just be using:
protocol Ownee {
associatedtype T: Ownee
var owner: Owner<T> { get }
}
2. In classes that conform to Ownee
, you must turn the abstract type of the associatedtype
into a concrete class by defining a typealias
:
class Student: Ownee {
typealias T = Student // <<-- define the property to be Owner<Student>
let professor: Professor
var owner: Owner<T> {
return professor
}
init(professor: Professor) {
self.professor = professor
}
}
3. Subclasses can now make use of the property, which will be of your defined type:
class FirstGradeStudent: Student {
func checkOwnerType() {
if self.owner is Owner<Student> { //warning: 'is' test is always true
print("yeah!")
}
}
}
Solution 2:[2]
The following syntax should support what you're after:
protocol Ownee {
associatedtype Owned = Self where Owned:Ownee
var owner: Owner<Owned> { get }
}
(tested using Swift 4 - Beta 1)
Solution 3:[3]
What happens if Student
is subclassed? The owner property remains Owner<Student>
, but Student != StudentSubclass
.
By making your Student
class conform to the Ownee
protocol, you must satisfy the contract of the protocol. The Ownee
protocol states that Owner
has type constraint, such that the Owner
generic type is the type that's conforming to Ownee
(Student
, in this case).
If the compiler were to allow subclassing (i.e. by allowing you to not make Student
final), then it would be possible for a StudentSubclass
to exist. Such a subclass would inherit the Owner
property, of type Owner<Student>
, but Student
is not the same as StudentSubclass
. The Ownee
protocol's contract has been breached, thus, such a subclass cannot be allowed to exist.
Solution 4:[4]
If you are coming to this thread just looking for a way to have subclasses inherit a method referring to its own type (type of Self) one workaround would be to parameterize the parent class on the child's type. e.g.
class Foo<T> {
func configure(_ block: @escaping (T)->Void) { }
}
class Bar: Foo<Bar> { }
Bar().configure { $0... }
BUT... it also seems that you can add a method in a protocol extension that would not be considered to conform in the protocol itself. I don't fully understand why this works:
protocol Configurable {
// Cannot include the method in the protocol definition
//func configure(_ block: @escaping (Self)->Void)
}
public extension Configurable {
func configure(_ block: @escaping (Self)->Void) { }
}
class Foo: Configurable { }
class Bar: Foo { }
Bar().configure { $0... }
If you uncomment the method in the protocol definition itself you'll get the Protocol 'Configurable' requirement 'configure' cannot be satisfied by a non-final class ('Foo') because it uses 'Self' in a non-parameter, non-result type position
compile error.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | David James |
Solution 3 | Alexander |
Solution 4 |