一尘不染

调用Swift协议扩展方法代替在子类中实现的方法

swift

我遇到了以下代码(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

因此,我希望在调用后应打印 _“ SubClassmethodA”_文本object1.methodB()。但是由于某种原因,methodA()调用了from协议扩展的默认实现。但是,object2.methodB()呼叫按预期方式工作。

是协议方法调度中的另一个Swift错误,还是我丢失了一些东西并且代码正常工作?


阅读 443

收藏
2020-07-07

共1个答案

一尘不染

这就是协议当前调度方法的方式。

协议见证表(有关更多信息,请参见WWDC谈话)用于在协议类型的实例上被调用时动态地调度到协议要求的实现。实际上,这只是功能实现的清单,以针对给定的符合类型调用协议的每种要求。

声明其对协议一致性的每种类型都有其自己的协议见证表。您会注意到,我说的是“说明其一致性”,而不仅仅是“符合”。BaseClass获得自己的协议见证表以符合要求MyProtocol。但是,SubClass
没有 获得自己的表来符合MyProtocol–相反,它仅依赖于BaseClass。如果将
: MyProtocol下移到的定义SubClass,它将具有自己的PWT。

因此,我们仅需考虑的是PWT的BaseClass外观。那么,它不会为任何的协议要求提供一个实现methodA()methodB()-因此它依赖于该协议扩展的实现。这意味着BaseClass符合的PWT
MyProtocol仅包含到扩展方法的映射。

因此,当methodB()调用extension
方法并将其发出到时methodA(),它会通过PWT动态分派该调用(因为它是在协议类型的实例上调用,即self)。因此,当SubClass实例发生这种情况时,我们将进行BaseClassPWT。因此methodA(),无论是否SubClass提供扩展实现,我们最终都会调用的扩展实现。

现在让我们考虑的PWT JustClass。它提供的实现methodA(),因此,其对符合PWT MyProtocol具有
实施作为映射methodA(),以及对于扩展实现methodB()。因此,当methodA()通过其PWT动态调度时,我们最终实现了

正如我在本问答中所提到的,子类的这种行为没有为其超类所遵循的协议获取自己的PWT确实有些令人惊讶,并且已被记录为bug。正如Swift团队成员Jordan
Rose在错误报告的评论中所说的那样,其背后的原因是

[…]子类无法提供新成员来满足一致性要求。这很重要,因为可以将协议添加到一个模块中的基类,而可以将其添加到另一个模块中。

因此,如果这是行为,那么已经编译的子类将缺少事实之后在另一个模块中添加的超类一致性中的任何PWT,这将是有问题的。


正如其他人已经说过的,这种情况下的一种解决方案是BaseClass提供自己的实现methodA()。现在,此方法将位于BaseClass的PWT中,而不是扩展方法中。

尽管当然,因为我们在这里处理 ,所以它不只是BaseClass‘列出方法的实现-
而是一个笨拙的东西,然后通过该类动态分配vtable(类实现该机制的机制多态性)。因此,对于一个SubClass实例,我们最终将调用其的覆盖methodA()

2020-07-07