我遇到了以下代码中解释的问题(Swift 3.1):
protocol MyProtocol {
func methodA()
func methodB()
}
extension MyProtocol {
func methodA() {
print("Default methodA")
}
func methodB() {
methodA()
}
}
// Test 1
class BaseClass: MyProtocol {
}
class SubClass: BaseClass {
func methodA() {
print("SubClass methodA")
}
}
let object1 = SubClass()
object1.methodB()
//
// Test 2
class JustClass: MyProtocol {
func methodA() {
print("JustClass methodA")
}
}
let object2 = JustClass()
object2.methodB()
//
// Output
// Default methodA
// JustClass methodA
所以我希望 "SubClass methodA" 文本应该在 object1.methodB()
调用之后打印。但是由于某种原因,调用了协议(protocol)扩展中 methodA()
的默认实现。然而,object2.methodB()
调用按预期工作。
这是协议(protocol)方法调度中的另一个 Swift 错误,还是我遗漏了一些东西而代码正常工作?
最佳答案
这就是协议(protocol)当前调度方法的方式。
协议(protocol)见证表(请参阅 this WWDC talk 了解更多信息)用于在协议(protocol)类型实例上调用时动态分派(dispatch)协议(protocol)要求的实现。它实际上只是一个函数实现列表,用于为给定的符合类型调用协议(protocol)的每个要求。
声明其符合协议(protocol)的每种类型都有自己的协议(protocol)见证表。您会注意到我说的是“陈述其符合性”,而不仅仅是“符合”。 BaseClass
获取自己的协议(protocol)见证表以符合 MyProtocol
。然而 SubClass
确实没有获得自己的表以符合 MyProtocol
– 相反,它只是依赖于 BaseClass
的.如果您将
: MyProtocol
移动到 SubClass
的定义中,它将拥有自己的 PWT。
所以我们在这里必须考虑的是 BaseClass
的 PWT 是什么样的。好吧,它没有为协议(protocol)要求 methodA()
或 methodB()
提供实现——因此它依赖于协议(protocol)扩展中的实现。这意味着符合 MyProtocol
的 BaseClass
的 PWT 只包含到扩展方法的映射。
因此,当调用扩展 methodB()
方法时,调用 methodA()
,它会通过 PWT 动态调度该调用(因为它是在协议(protocol)类型的实例上被调用;即 self
)。因此,当 SubClass
实例发生这种情况时,我们将通过 BaseClass
的 PWT。因此,我们最终调用了 methodA()
的扩展实现,而不管 SubClass
是否提供了它的实现。
现在让我们考虑JustClass
的PWT。它提供了 methodA()
的实现,因此其符合 MyProtocol
的 PWT 具有 that 实现作为 methodA() 的映射
,以及 methodB()
的扩展实现。因此,当 methodA()
通过其 PWT 动态调度时,我们将在其实现中结束。
正如我所说 in this Q&A , 子类的这种行为没有为它们的父类(super class)符合的协议(protocol)获得自己的 PWT 确实有些令人惊讶,并且一直是 filed as a bug .正如 Swift 团队成员 Jordan Rose 在错误报告的评论中所说,其背后的原因是
[...] The subclass does not get to provide new members to satisfy the conformance. This is important because a protocol can be added to a base class in one module and a subclass created in another module.
因此,如果这是这种行为,则已编译的子类将缺少来自父类(super class)一致性的任何 PWT,这些 PWT 是在另一个模块中添加的,这将是有问题的。
正如其他人已经说过的,这种情况下的一种解决方案是让 BaseClass
提供它自己的 methodA()
实现。此方法现在将位于 BaseClass
的 PWT 中,而不是扩展方法中。
当然,因为我们在这里处理类,所以它不会只是BaseClass
对所列方法的实现——相反,它将是一个thunk然后通过类的 vtable 动态调度(类实现多态性的机制)。因此,对于 SubClass
实例,我们最终将调用其对 methodA()
的覆盖。
关于调用 Swift 协议(protocol)扩展方法而不是子类中实现的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47851909/