Swift 协议扩展方法被调用而不是子类中实现的方法
Swift protocol extension method is called instead of method implemented in subclass
我遇到了以下代码中解释的问题 (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()
的默认实现。但是 object2.methodB()
调用按预期工作。
这是协议方法调度中的另一个 Swift 错误,还是我遗漏了一些东西并且代码工作正常?
好吧,我想 subclass 方法 A 不是多态的,因为您不能在其上放置 override 关键字,因为 class 不知道该方法是在扩展中实现的协议,因此不允许您覆盖它。扩展方法可能会在运行时踩踏您的实现,就像 objective C 中 2 个确切的类别方法以未定义的行为相互胜过一样。您可以通过在模型中添加另一层并在 class 而不是协议扩展中实现方法来修复此行为,从而从中获得多态行为。缺点是您不能在该层中保留未实现的方法,因为没有对抽象 classes 的本机支持(这实际上是您尝试使用协议扩展所做的)
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
这里也有相关的回答:
在您的代码中,
let object1 = SubClass()
object1.methodB()
您从 SubClass
的实例调用了 methodB,但 SubClass
没有任何名为 methodB
的方法。然而它的超级 class, BaseClass
符合 MyProtocol
,它有一个 methodB
methodB.
因此,它将从 MyProtocal
调用 methodB
。因此它将在 extesion MyProtocol
.
中执行 methodA
要达到您的预期,您需要在 BaseClass
中实现 methodA
并在 SubClass
中覆盖它,如以下代码
class BaseClass: MyProtocol {
func methodA() {
print("BaseClass methodA")
}
}
class SubClass: BaseClass {
override func methodA() {
print("SubClass methodA")
}
}
现在,输出会变成
//Output
//SubClass methodA
//JustClass methodA
虽然方法可以达到你的预期,但我不确定这种代码结构是否值得推荐。
这就是协议当前调度方法的方式。
协议见证 table(请参阅 this WWDC talk 了解更多信息)用于在协议类型实例上调用时动态分派协议要求的实现。所有这一切,实际上只是一个函数实现的列表,用于为给定的符合类型调用协议的每个要求。
声明其符合协议的每种类型都有自己的协议见证 table。您会注意到我说的是 "states its conformance",而不仅仅是 "conforms to"。 BaseClass
获得自己的协议见证 table 以符合 MyProtocol
。然而 SubClass
而不是 获得自己的 table 以符合 MyProtocol
– 相反,它只是依赖于 BaseClass
。如果您将
: MyProtocol
向下移动到 SubClass
的定义,它将拥有自己的 PWT。
所以我们在这里必须考虑的是 BaseClass
的 PWT 是什么样子的。好吧,它没有为协议要求 methodA()
或 methodB()
提供实现——因此它依赖于协议扩展中的实现。这意味着 BaseClass
的 PWT 符合 MyProtocol
只包含到扩展方法的映射。
因此,当调用扩展 methodB()
方法并调用 methodA()
时,它会通过 PWT 动态调度该调用(因为它是在协议类型的实例上调用的; 即 self
)。因此,当 SubClass
实例发生这种情况时,我们将通过 BaseClass
的 PWT。所以我们最终调用了 methodA()
的扩展实现,而不管 SubClass
提供了它的实现。
现在让我们考虑JustClass
的PWT。它提供了 methodA()
的实现,因此它的 PWT 符合 MyProtocol
具有 that 实现作为 methodA()
的映射,以及扩展methodB()
的实施。因此,当 methodA()
通过其 PWT 动态调度时,我们最终会进入 其 实现。
正如我所说 in this Q&A, this behaviour of subclasses not getting their own PWTs for protocols that their superclass(es) conform to is indeed somewhat surprising, and has been 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.
因此,如果这是行为,则已编译的子classes 将缺少来自超级class 一致性的任何 PWT,这些 PWT 是事后添加到另一个模块中的,这将是有问题的。
正如其他人已经说过的,在这种情况下,一种解决方案是让 BaseClass
提供自己的 methodA()
实现。此方法现在将在 BaseClass
的 PWT 中,而不是扩展方法中。
虽然当然,因为我们在这里处理classes,它不会只是BaseClass
方法的实现已列出 - 相反,它将是一个 thunk,然后通过 class' vtable 动态调度(classes 实现多态性的机制)。因此,对于 SubClass
实例,我们最终会调用其对 methodA()
.
的覆盖
一位朋友与我分享的一个非常简短的答案是:
Only the class that declares the conformance gets a protocol witness table
意味着具有该功能的 subclass 对协议见证 table 的设置方式没有影响。
协议见证只是协议、它的扩展和实现它的具体 class 之间的契约。
我遇到了以下代码中解释的问题 (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()
的默认实现。但是 object2.methodB()
调用按预期工作。
这是协议方法调度中的另一个 Swift 错误,还是我遗漏了一些东西并且代码工作正常?
好吧,我想 subclass 方法 A 不是多态的,因为您不能在其上放置 override 关键字,因为 class 不知道该方法是在扩展中实现的协议,因此不允许您覆盖它。扩展方法可能会在运行时踩踏您的实现,就像 objective C 中 2 个确切的类别方法以未定义的行为相互胜过一样。您可以通过在模型中添加另一层并在 class 而不是协议扩展中实现方法来修复此行为,从而从中获得多态行为。缺点是您不能在该层中保留未实现的方法,因为没有对抽象 classes 的本机支持(这实际上是您尝试使用协议扩展所做的)
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
这里也有相关的回答:
在您的代码中,
let object1 = SubClass()
object1.methodB()
您从 SubClass
的实例调用了 methodB,但 SubClass
没有任何名为 methodB
的方法。然而它的超级 class, BaseClass
符合 MyProtocol
,它有一个 methodB
methodB.
因此,它将从 MyProtocal
调用 methodB
。因此它将在 extesion MyProtocol
.
methodA
要达到您的预期,您需要在 BaseClass
中实现 methodA
并在 SubClass
中覆盖它,如以下代码
class BaseClass: MyProtocol {
func methodA() {
print("BaseClass methodA")
}
}
class SubClass: BaseClass {
override func methodA() {
print("SubClass methodA")
}
}
现在,输出会变成
//Output
//SubClass methodA
//JustClass methodA
虽然方法可以达到你的预期,但我不确定这种代码结构是否值得推荐。
这就是协议当前调度方法的方式。
协议见证 table(请参阅 this WWDC talk 了解更多信息)用于在协议类型实例上调用时动态分派协议要求的实现。所有这一切,实际上只是一个函数实现的列表,用于为给定的符合类型调用协议的每个要求。
声明其符合协议的每种类型都有自己的协议见证 table。您会注意到我说的是 "states its conformance",而不仅仅是 "conforms to"。 BaseClass
获得自己的协议见证 table 以符合 MyProtocol
。然而 SubClass
而不是 获得自己的 table 以符合 MyProtocol
– 相反,它只是依赖于 BaseClass
。如果您将
: MyProtocol
向下移动到 SubClass
的定义,它将拥有自己的 PWT。
所以我们在这里必须考虑的是 BaseClass
的 PWT 是什么样子的。好吧,它没有为协议要求 methodA()
或 methodB()
提供实现——因此它依赖于协议扩展中的实现。这意味着 BaseClass
的 PWT 符合 MyProtocol
只包含到扩展方法的映射。
因此,当调用扩展 methodB()
方法并调用 methodA()
时,它会通过 PWT 动态调度该调用(因为它是在协议类型的实例上调用的; 即 self
)。因此,当 SubClass
实例发生这种情况时,我们将通过 BaseClass
的 PWT。所以我们最终调用了 methodA()
的扩展实现,而不管 SubClass
提供了它的实现。
现在让我们考虑JustClass
的PWT。它提供了 methodA()
的实现,因此它的 PWT 符合 MyProtocol
具有 that 实现作为 methodA()
的映射,以及扩展methodB()
的实施。因此,当 methodA()
通过其 PWT 动态调度时,我们最终会进入 其 实现。
正如我所说 in this Q&A, this behaviour of subclasses not getting their own PWTs for protocols that their superclass(es) conform to is indeed somewhat surprising, and has been 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.
因此,如果这是行为,则已编译的子classes 将缺少来自超级class 一致性的任何 PWT,这些 PWT 是事后添加到另一个模块中的,这将是有问题的。
正如其他人已经说过的,在这种情况下,一种解决方案是让 BaseClass
提供自己的 methodA()
实现。此方法现在将在 BaseClass
的 PWT 中,而不是扩展方法中。
虽然当然,因为我们在这里处理classes,它不会只是BaseClass
方法的实现已列出 - 相反,它将是一个 thunk,然后通过 class' vtable 动态调度(classes 实现多态性的机制)。因此,对于 SubClass
实例,我们最终会调用其对 methodA()
.
一位朋友与我分享的一个非常简短的答案是:
Only the class that declares the conformance gets a protocol witness table
意味着具有该功能的 subclass 对协议见证 table 的设置方式没有影响。
协议见证只是协议、它的扩展和实现它的具体 class 之间的契约。