Implementing Swift Protocol Methods in a Base Class
I have a Swift protocol that defines a method similar to the following:
protocol MyProtocol {
class func retrieve(id:String) -> Self?
}
I have several different classes that will follow this protocol:
class MyClass1 : MyProtocol { ... }
class MyClass2 : MyProtocol { ... }
class MyClass3 : MyProtocol { ... }
The method implementation retrieve
in each subclass will be almost identical. I would like to pull the general implementation of these functions into a generic superclass that conforms to the protocol:
class MyBaseClass : MyProtocol
{
class func retrieve(id:String) -> MyBaseClass?
}
class MyClass1 : MyBaseClass { ... }
class MyClass2 : MyBaseClass { ... }
class MyClass3 : MyBaseClass { ... }
The problem with this approach is that my protocol defines the return type of the method retrieve
as the type Self
I actually want at the end. However, as a result, I cannot implement retrieve
in the base class because it throws compiler errors for MyClass1
, MyClass2
and MyClass3
. Each of these classes must conform to the protocol they inherit from MyBaseClass
. But since the method is implemented with a return type MyBaseClass
and the protocol requires it to be MyClass1
, it says my class is not protocol compliant.
I'm wondering if there is a clean way to implement a protocol method that refers to a type Self
in one or more of its methods from the base class. I could, of course, implement the method differently in the base class, and then each subclass implement the protocol by calling its superclass method to do the job, but that doesn't seem particularly smart to me.
Is there an easier approach I'm missing here?
source to share
This should work:
protocol MyProtocol {
class func retrieve(id:String) -> Self?
}
class MyBaseClass: MyProtocol {
required init() { }
class func retrieve(id:String) -> Self? {
return self()
}
}
required init() { }
is necessary to ensure that any subclasses derived from MyBaseClass
have an initializer init()
.
Please note that this code causes Swift Playground to crash. I do not know why. So try with a real project.
source to share
Not sure what you want to do here, just with your example, so a solution can be found here:
protocol a : class {
func retrieve(id: String) -> a?
}
class b : a {
func retrieve(id: String) -> a? {
return self
}
}
Justification
protocol a : class
so only reference types can be extensions. You probably don't want to pass value types (struct) when dealing with your classes.
source to share
I gave the answer from @rintaro as the correct answer because he answered the question as I asked for it. However, I found this solution to be too limiting, so I am posting an alternate answer that I thought works for others facing this problem.
A limitation of the previous answer is that it only works if the type represented Self
(in my example, which would be MyClass1
, MyClass2
or MyClass3
) is used in the protocol or as a return type from a class method. So when I have this method
class func retrieve(id:String) -> Self?
everything works as i hoped. However, when I worked with this, I realized that this method must now be asynchronous and cannot directly return a result. So I tried it with a class method:
class func retrieve(id:String, successCallback:(Self) -> (), failureCallback:(NSError) -> ())
I can put this method in MyProtocol
, but when I try to implement in MyBaseClass
, I get the following compiler error:
Error:(57, 36) 'Self' is only available in a protocol or as the result of a class method; did you mean 'MyBaseClass'?
So I really can't use this approach if the type being referenced Self
is used in a very specific way.
After some experimentation and a lot of research on SO, I was finally able to get something good using generics. I have defined a method in my protocol like this:
class func retrieve(id:String, successCallback:(Self) -> (), failureCallback:(NSError) -> ())
and then in my base class I do the following:
class MyBaseClass : MyProtocol {
class func retrieve<T:MyBaseClass>(id:String, successCallback: (T) -> (), failureCallback: (NSError) -> ()) {
// Perform retrieve logic and on success invoke successCallback with an object of type `T`
}
}
When I want to get an instance of a type MyClass1
, I do the following:
class MyClass1 : MyBaseClass {
func success(result:MyClass1} {
...
}
func failure(error:NSError) {
...
}
class func doSomething {
MyClass1.retrieve("objectID", successCallback:success, failureCallback:failure)
}
In this implementation, the type of the function for success
tells the compiler which type to use for T
in the implementation retrieve
in MyBaseClass
.
source to share