使用某个协议作为符合另一个协议的具体类型是不被支持的。

65

我试图将泛型与协议混合使用,但遇到了很大的困难 xD。

我在一个 Android/Java 项目中实现了特定的架构,并尝试对其进行重写以适应 Swift/iOS 项目。但是我发现了这个限制。

ProtocolA

protocol ProtocolA {

}

协议B

protocol ProtocolB : ProtocolA {

}

实现协议A

class ImplementProtocolA <P : ProtocolA> {

    let currentProtocol : P

    init(currentProtocol : P) {
        self.currentProtocol = currentProtocol
    }

}

实现协议B

class ImplementProtocolB : ImplementProtocolA<ProtocolB> {

}

因此,当我尝试将ProtocolB设置为实现ProtocolA的具体类型时,我会收到以下错误:

不支持将“ProtocolB”用作符合协议“ProtocolA”的具体类型

1 这种“限制”有什么原因吗?

2 是否有任何解决方法可以实现这个目标?

3 最终是否会支持这种方式?

--已更新--

我认为这是同样问题的另一种变体:

查看协议

protocol View {

}

protocol GetUserView : View {
    func showProgress()
    func hideProgress()
    func showError(message:String)
    func showUser(userDemo:UserDemo)
}

演示者协议

protocol Presenter {
    typealias V : View
}

class UserDemoPresenter : Presenter {
    typealias V = GetUserView
}

错误:

UserDemoPresenter.swift中可能意图匹配的 'V' (也称为 'GetUserView') 不符合 'View'。

这是怎么回事?它符合啊!

即使我使用 View 而不是 GetUserView,它还是无法编译。

class UserDemoPresenter : Presenter {
    typealias V = View
}

UserDemoPresenter.swift中可能意图匹配'V'(即'View'),但未符合'View'的要求。

xxDD我真的不明白。

--更新--

使用Rob Napier提出的解决方案并不能解决问题,只是延迟了问题的出现。

当我试图定义对UserDemoPresenter的引用时,需要指定泛型类型,因此我会得到相同的错误:

private var presenter : UserDemoPresenter<GetUserView>

不支持将“GetUserView”用作符合协议“GetUserView”的具体类型。


2
还在这里观察到:https://dev59.com/wFwY5IYBdhLWcg3wAj09。 - Martin R
1个回答

63
限制的根本原因在于Swift没有一级元类型。最简单的例子是这样无法工作:
func isEmpty(xs: Array) -> Bool {
    return xs.count == 0
}

理论上,这段代码是可以工作的,如果它能够工作的话,我可以创建许多其他类型(例如Functor和Monad,这些类型在今天的Swift中无法表达)。但是你不能这样做。你需要帮助Swift将其固定为具体类型。通常我们使用泛型来实现这一点:

func isEmpty<T>(xs: [T]) -> Bool {
    return xs.count == 0
}

请注意,在这里T是完全冗余的。我没有必要表达它;它从未被使用。但是,Swift需要它才能将抽象的Array转换为具体的[T]。在你的情况下也是如此。
这是一个具体类型(好吧,它是一个抽象类型,将在任何时候实例化并填充P时转换为具体类型):
class ImplementProtocolA<P : ProtocolA>

这是一个完全抽象的类型,Swift没有任何规则将其转化为具体类型:
class ImplementProtocolB : ImplementProtocolA<ProtocolB>

您需要将其具体化。这样可以编译:

class ImplementProtocolB<T: ProtocolB> : ImplementProtocolA<T> {}

同时:

class UserDemoPresenter<T: GetUserView> : Presenter {
    typealias V = T
}

由于您可能会在以后遇到这个问题:如果您将这些结构体或final类制作,您的生活将变得更加轻松。混合协议、泛型和类多态性充满了非常尖锐的边缘。有时你很幸运,它只是不会编译。有时它会调用你意想不到的东西。
您可能会对A Little Respect for AnySequence感兴趣,其中详细介绍了一些相关问题。
private var presenter : UserDemoPresenter<GetUserView>

这仍然是一个抽象类型。你的意思是:

final class Something<T: GetUserView> {
    private var presenter: UserDemoPresenter<T>
}

如果这会造成问题,您需要创建一个盒子。请参阅Protocol doesn't conform to itself?以了解如何进行类型擦除,以便可以持有抽象类型。但是你需要使用具体类型来工作。在大多数情况下,您不能最终专门针对协议。您必须最终专门针对某些具体内容。

非常感谢您提供的解决方案和解释。您能否提供一个使用结构体而不是类的代码示例,以便我完全理解您所建议的内容? - Víctor Albertos
1
我指的是在你的代码中用“struct”代替“class”。如果你使用“struct”,你将得到值语义(当你将它传递给函数时,函数会得到自己独立的副本)。如果你需要引用语义(当你将它传递给函数时,函数对它所做的修改被调用者看到),那么请使用“final class”。“struct”和“final class”都禁止子类化,这使事情变得更容易。(子类化带来了很多疯狂的东西。) - Rob Napier
好的,谢谢!我将进行课程最终项目,实际上这是原始Java项目中的最终项目。但我认为我将继续使用类而不是结构体,我需要让这个项目尽可能地模仿Java项目中定义的行为,而将类更改为结构体可能会导致非常大的不同情况 xD。 - Víctor Albertos
这是一种完全抽象的类型,Swift没有任何规则将其转换为具体类型:class ImplementProtocolB : ImplementProtocolA<ProtocolB>' 嘿,罗布,你能详细说明一下这不是具体类型的原因吗?谢谢。 - Mercurial
1
@Mercurial Swift 不知道 ProtocolB 的实际实现,因此无法确定要分配多少存储空间来容纳“稍后将传递给我但可能是任意大小的符合 ProtocolB 的东西”。Swift 可以自动创建一个间接盒子来存储它(这就是它处理带有协议类型的变量的方式),但这不是 Swift 当前的一部分。它需要一个具体的类型(即它知道最终大小的类型)。 - Rob Napier
显示剩余3条评论

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