Reputation: 16399
This code produces Xcode error messages that lead you in a circle. Let's say I have a protocol called Marker
and I'd like Markers to copy themselves. Here's a first guess...
protocol Marker {
func copy() -> Self
}
class Marker1 : Marker {
func copy() -> Self {
return Marker1() // error here
}
}
(I'm not sure how to use Self
correctly, because I can't find it in the The Swift Programming Language document. If you know where it's documented, please include that in answer.)
That code gives an error on the marked line: Cannot convert return expression of type 'Marker1' to return type 'Self'
and it suggests a fix: Insert ' as! Self'
.
I accept the fix:
...
return Marker1() as! Self
...
This leads to another compiler error: 'Self' is only available in a protocol or as the result of a method in a class; did you mean 'Marker1'?
If I accept that "fix" it goes back to the original error. I'd call that a bug in Xcode. Let's try something else:
func copy() -> Marker1 {
return Marker1()
}
Another error: Method 'copy()' in non-final class 'Marker1' must return `Self` to conform to protocol 'Marker'
Making the class final
does fix the error. But is there a way to do this without making a class final? And where is Self
documented?
Upvotes: 4
Views: 2609
Reputation: 130072
With such a hierarchy, you would have to make the class conforming to the protocol final
:
protocol Marker {
func copy() -> Self
}
final class Marker1 : Marker {
func copy() -> Marker1 {
return Marker1()
}
}
The final
is needed because when you don't apply final
and you create a subclass Marker2: Marker1
then copy would not return the correct class Self
any more.
You can workaround that by creating a required
initializer and always create the correct instance:
protocol Marker {
init()
func copy() -> Self
}
class Marker1 : Marker {
required init() {
}
func copy() -> Self {
let copy = type(of: self).init()
return copy
}
}
(original code removed because does not work)
Related: Implementing copy() in Swift
Upvotes: 5
Reputation: 54706
The issue with your current implementation is that Self
is an abstract type, so you cannot simply return a concrete type, such as Marker1
from a function with a return type of Self
. If you changed the return type to Marker1
, you'd need to make your class final to ensure that no subclass can override the method, since in a subclass, Self
would correspond to the subclass type. However, this is still not good since in this case the subclass won't conform to the protocol, since in a subclass of Marker1
, the return type of Marker1
is not the same as self
.
You can solve this problem by getting the metatype of your current class using type(of: self)
, then calling a designated initializer on the metatype, which will indeed return an instance of type Self
. The final step is to create a required initializer for your class to ensure that all subclasses need to implement the same initializer that's called in your copy()
method in order to make copy
work for subclasses as well.
protocol Marker {
func copy() -> Self
}
class Marker1 : Marker {
func copy() -> Self {
let result = type(of: self).init()
return result
}
required init() {}
}
Upvotes: 1