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

Igo*_*gin 19 protocols 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
Run Code Online (Sandbox Code Playgroud)

所以我希望在调用后打印"SubClass methodA"文本object1.methodB().但由于某种原因,methodA()调用了协议扩展的默认实现.但是,object2.methodB()呼叫按预期工作.

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

Ham*_*ish 30

这就是协议当前如何调度方法.

使用协议见证表(参见此WWDC对话以获取更多信息),以便在协议类型实例上调用时动态调度协议要求的实现.它实际上只是函数实现的列表,用于为给定的符合类型调用协议的每个要求.

声明其与协议一致性的每种类型都有自己的协议见证表.你会注意到我说"陈述其符合性",而不仅仅是"符合".BaseClass获得自己的协议见证表以符合MyProtocol.然而SubClass,没有得到自己的表符合MyProtocol- 相反,它只是依赖于BaseClass.如果你
: MyProtocol向下移动到定义SubClass,它将拥有自己的PWT.

所以我们在这里要考虑的是PWT的BaseClass外观.好吧,它没有为任何一个协议要求提供实现,methodA()或者methodB()- 它依赖于协议扩展中的实现.这意味着BaseClass符合的PWT MyProtocol只包含扩展方法的映射.

因此,当methodB()调用扩展方法并进行调用时methodA(),它会通过PWT动态调度该调用(因为它是在协议类型的实例上调用的;即self).因此,当一个SubClass实例发生这种情况时,我们将通过BaseClassPWT.所以我们最终调用扩展实现methodA(),而不管SubClass提供它的实现的事实.

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

正如我在这个Q&A中所说的那样,子类的这种行为没有为他们的超类符合的协议获得他们自己的PWT确实有点令人惊讶,并且已经被归档为bug.正如Swift团队成员Jordan Rose在错误报告的评论中所说的那样,其背后的原因是

[...]子类无法提供新成员来满足一致性.这很重要,因为协议可以添加到一个模块中的基类和另一个模块中创建的子类.

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


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

虽然当然,因为我们在这里处理,它不仅仅是BaseClass列出的方法的实现 - 而是它将成为一个thunk,然后动态调度类'vtable(类实现的机制)多态).因此,对于一个SubClass例子,我们最终会调用它的覆盖methodA().