Tim Dean
Tim Dean

Reputation: 8292

Implementing Swift protocol methods in a base class

I have a Swift protocol that defines a method like the following:

protocol MyProtocol {

    class func retrieve(id:String) -> Self?
}

I have several different classes that will conform to this protocol:

class MyClass1 : MyProtocol { ... }
class MyClass2 : MyProtocol { ... }
class MyClass3 : MyProtocol { ... }

The implementation for the retrieve method in each subclass will be nearly identical. I'd like pull the common implementation of those functions into a shared 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 retrieve method as type Self, which is what I really want in the end. However, as a result I cannot implement retrieve in the base class this way because it causes compiler errors for MyClass1, MyClass2, and MyClass3. Each of those classes must conform to the protocol that they inherit from MyBaseClass. But because the method is implemented with a return type of MyBaseClass and the protocol requires it to be of MyClass1, it says that my class doesn't conform to the protocol.

I'm wondering if there is a clean way of implementing a protocol method that references a Self type in one or more of its methods from within a base class. I could of course implement a differently-named method in the base class and then have each subclass implement the protocol by calling into its superclass's method to do the work, but that doesn't seem particularly elegant to me.

Is there a more straightforward approach that I'm missing here?

Upvotes: 4

Views: 3005

Answers (3)

Tim Dean
Tim Dean

Reputation: 8292

I have marked the answer from @rintaro as the correct answer because it did answer the question as I asked it. However, I have found this solution to be too limiting so I'm posting the alternate answer I found to work here for any others running into this problem.

The limitation of the previous answer is that it only works if the type represented by Self (in my example that would be MyClass1, MyClass2, or MyClass3) is used in a protocol or as the return type from a class method. So when I have this method

class func retrieve(id:String) -> Self?

everything works as I hoped. However, as I worked through this I realized that this method now needs to be asynchronous and can't return the result directly. So I tried this with the class method:

class func retrieve(id:String, successCallback:(Self) -> (), failureCallback:(NSError) -> ())

I can put this method into 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 unless the type referenced by Self is used in very specific ways.

After some experimentation and lots of SO research, I was finally able to get something working better using generics. I defined the method in my protocol as follows:

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 retrieve an instance of the 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)
    }

With this implementation, the function type for success tells the compiler what type should be applied for T in the implementation of retrieve in MyBaseClass.

Upvotes: 0

GNewc
GNewc

Reputation: 445

Not sure what you're looking to accomplish here by just your example, so here's a possible solution:

protocol a : class {
  func retrieve(id: String) -> a?
}

class b : a {
  func retrieve(id: String) -> a? {
    return self
  }
}

The reasoning behind the

protocol a : class

declaration is so that only reference types can be extensions. You likely don't want to be passing around value types (struct) when you're dealing with your classes.

Upvotes: 0

rintaro
rintaro

Reputation: 51911

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 any subclasses derived from MyBaseClass has init() initializer.

Note that this code crashes Swift Playground. I don't know why. So try with real project.

Upvotes: 4

Related Questions