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?

+3


source to share


3 answers


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.

+4


source


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.

0


source


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

.

0


source







All Articles