调用 Swift 协议(protocol)扩展方法而不是子类中实现的方法

标签 swift inheritance protocols dynamic-dispatch

我遇到了以下代码中解释的问题(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)扩展中的实现。这意味着符合 MyProtocolBaseClass 的 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/

相关文章:

Python 3 : Returning a new child class instance from an inherited method, 当子构造函数的参数多于父构造函数时

ios - swift 2.0 - UITextFieldDelegate 协议(protocol)扩展不工作

xcode - 在 swift 中使用 xcpretty 命令运行 xcodebuild 时出错

swift - 何时在 Swift 中使用可选类型背后的原则?

ios - 将新创建的 View 链接到 Storyboard 中的 View Controller 类时出现问题

ios - 如何更改一个 ViewController 函数的属性?

python - 初始化子类中在父类中使用的属性

c++ - 为什么虚拟基类必须由最派生的类来构造?

url - URL 中的方案和协议(protocol)有什么区别?

iphone - 我是否需要 @protocol 声明或其他内容来确保子类定义选择器?