在基类中实现Swift协议方法

4

我有一个Swift协议,定义了如下方法:

protocol MyProtocol {

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

我有几个不同的类将符合此协议:

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

在每个子类中,“retrieve”方法的实现几乎是相同的。我希望将这些函数的通用实现提取到符合协议的共享超类中:
class MyBaseClass : MyProtocol
{
    class func retrieve(id:String) -> MyBaseClass?   
}

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

这种方法的问题在于,我的协议将retrieve方法的返回类型定义为Self类型,这正是我最终想要的。然而,结果是我不能以这种方式在基类中实现retrieve,因为它会导致MyClass1MyClass2MyClass3的编译错误。每个类都必须符合从MyBaseClass继承的协议。但是因为该方法的返回类型是MyBaseClass,而协议要求它是MyClass1,所以它说我的类不符合协议。
我想知道是否有一种干净的方法,在基类中实现一个引用一个或多个方法的Self类型的协议方法。当然,我可以在基类中实现一个不同命名的方法,然后让每个子类通过调用其超类的方法来实现协议来完成工作,但是这对我来说似乎不太优雅。
是否有更直接的方法我错过了什么?
3个回答

4
这应该可以工作:
protocol MyProtocol {
    class func retrieve(id:String) -> Self?
}

class MyBaseClass: MyProtocol {

    required init() { }

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

required init() { }是必要的,以确保任何派生自MyBaseClass的子类都具有init()初始化程序。

请注意,此代码会使Swift Playground崩溃。我不知道为什么。因此,请尝试使用实际项目。


1
我将这个答案标记为正确的,因为它确实回答了我提出的问题。然而,考虑到其他因素,我发现它对我的需求不够充分,这些因素我没有理解得足够好以便包含在问题中。我会发布自己的答案,并提供详细信息,以供有兴趣的人参考。 - Tim Dean

0

我将@rintaro的答案标记为正确答案,因为它确实回答了我提出的问题。然而,我发现这个解决方案过于局限性,所以我在这里发布我找到的替代答案,供其他遇到此问题的人使用。

先前答案的限制在于,它仅适用于由Self表示的类型(在我的示例中,这将是MyClass1MyClass2MyClass3)用作协议或作为类方法的返回类型时。因此,当我有这个方法时

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

一切都按照我的期望工作。然而,当我深入研究时,我意识到这种方法现在需要是异步的,不能直接返回结果。因此,我尝试使用类方法:

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

我可以将这个方法放入MyProtocol中,但是当我尝试在MyBaseClass中实现时,会出现以下编译器错误:

Error:(57, 36) 'Self' is only available in a protocol or as the result of a class method; did you mean 'MyBaseClass'?

所以,除非Self引用的类型在非常具体的情况下使用,否则我真的无法使用这种方法。

经过一些实验和大量的SO研究,最终我能够使用泛型让事情更好地运作。我在我的协议中定义了以下方法:

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

然后在我的基类中,我执行以下操作:

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`
    }
}

当我想要检索MyClass1类型的实例时,我会执行以下操作:

class MyClass1 : MyBaseClass {    
    func success(result:MyClass1} {
        ...
    }
    func failure(error:NSError) {
        ...
    }
    class func doSomething {
        MyClass1.retrieve("objectID", successCallback:success, failureCallback:failure)
    }

通过这个实现,success 函数类型告诉编译器在 MyBaseClass 中的 retrieve 实现中应该应用哪种类型来代替 T

0

不确定您在这里想要通过示例实现什么,因此这是一个可能的解决方案:

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

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

这个问题的原因在于

protocol a : class

声明是为了只有引用类型可以作为扩展。当您处理类时,您可能不希望传递值类型(结构体)。


这并不完全符合我的要求,因为它告诉Swift我正在返回一个实现协议"a"的对象,而不是返回一个类b的实例。此外,它不允许我实现一个名为"c"的"b"子类,并使检索方法返回"c"的实例。 - Tim Dean

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接