28

我遇到了以下代码(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()文本。methodA()但是由于某种原因,调用了 from 协议扩展的默认实现。但是object2.methodB()呼叫按预期工作。

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

4

4 回答 4

47

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

协议见证表(有关更多信息,请参阅此 WWDC 演讲)用于在协议类型实例上调用时动态调度协议要求的实现。它实际上只是一个函数实现的列表,以调用给定的符合类型的协议的每个要求。

声明其符合协议的每种类型都有自己的协议见证表。你会注意到我说的是“声明它的一致性”,而不仅仅是“符合”。BaseClass获得自己的协议见证表以符合MyProtocol. 然而SubClass,它并没有得到自己的表来遵守MyProtocol——相反,它只是依赖于BaseClass's. 如果将
: MyProtocol下移到 的定义SubClass,它将拥有自己的 PWT。

所以我们在这里只需要考虑 PWT 的BaseClass外观。好吧,它没有提供任何协议要求的实现,methodA()或者methodB()——因此它依赖于协议扩展中的实现。这意味着BaseClass符合的 PWTMyProtocol仅包含到扩展方法的映射。

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

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

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

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

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


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

当然,因为我们在这里处理,所以它不仅仅是BaseClass列出的方法的实现——相反,它将是一个thunk,然后通过类的 vtable 动态调度(类实现的机制多态性)。因此,举个SubClass例子,我们最终会调用它的覆盖methodA()

于 2017-06-22T17:36:34.503 回答
2

一位朋友与我分享的一个非常简短的答案是:

只有声明一致性的类才能获得协议见证表

这意味着具有该功能的子类对协议见证表的设置方式没有影响。

协议见证只是协议、它的扩展和实现它的具体类之间的契约。

于 2021-11-09T21:34:41.317 回答
0

好吧,我想子类方法 A 不是多态的,因为您不能将 override 关键字放在它上面,因为该类不知道该方法是在协议的扩展中实现的,因此不允许您覆盖它。扩展方法可能会在运行时踩到您的实现,就像 2 个确切的类别方法在目标 C 中以未定义的行为相互胜过对方。您可以通过在模型中添加另一层并在类中实现方法而不是在类中实现方法来修复此行为协议扩展,从而从中获得多态行为。缺点是您不能在该层中保留未实现的方法,因为没有对抽象类的本机支持(这实际上是您尝试使用协议扩展做的事情)

protocol MyProtocol {
    func methodA()
    func methodB()
}

class MyProtocolClass: MyProtocol {
    func methodA() {
        print("Default methodA")
    }

    func methodB() {
        methodA()
    }
}

// Test 1
class BaseClass: MyProtocolClass {

}

class SubClass: BaseClass {
    override func methodA() {
        print("SubClass methodA")
    }
}


let object1 = SubClass()
object1.methodB()
//

// Test 2
class JustClass: MyProtocolClass {
    override func methodA() {
        print("JustClass methodA")
    }
}

let object2 = JustClass()
object2.methodB()
//
// Output
// SubClass methodA
// JustClass methodA

这里也有相关的答案:Swift Protocol Extensions overriding

于 2017-06-22T15:17:24.593 回答
0

在您的代码中,

let object1 = SubClass()
object1.methodB()

您从 的实例调用了 methodB SubClass,但SubClass没有任何名为 的方法methodB。然而它的超类,BaseClassconform to MyProtocol,它有一个methodBmethodB。

因此,它将调用methodBfrom MyProtocal。因此它将执行methodAin extesion MyProtocol

要达到您的期望,您需要实现methodAinBaseClass并覆盖它 in SubClass,如以下代码

class BaseClass: MyProtocol {
    func methodA() {
        print("BaseClass methodA")
    }
}

class SubClass: BaseClass {
    override func methodA() {
        print("SubClass methodA")
    }
}

现在,输出将变为

//Output
//SubClass methodA
//JustClass methodA

虽然该方法可以达到您的期望,但我不确定这种代码结构是否值得推荐。

于 2017-06-22T15:26:22.507 回答