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 之间的契约。