应该使用谁的见证表?

Roh*_*ale 6 dispatch swift

我想对 Swift 中的方法调度有一个深入的了解。我从这个流行的博客中读到了以下三种类型的调度:

  1. 动态的
  2. 表(Swift 中的见证表)
  3. 信息

在那篇博客中,作者说 NSObject 子类型维护一个调度表(见证表)和一个消息调度层次结构。作者分享的代码片段如下:

class Person: NSObject {
    func sayHi() {
        print("Hello")
    }
}
func greetings(person: Person) {
    person.sayHi()
}

greetings(person: Person()) // prints 'Hello'

class MisunderstoodPerson: Person {}
extension MisunderstoodPerson {
    override func sayHi() {
        print("No one gets me.")
    }
}
greetings(person: MisunderstoodPerson()) // prints 'Hello'
Run Code Online (Sandbox Code Playgroud)

我将引用作者对 Person 实例调用 sayHi()的推理:

greetings(person:) 方法使用表调度来调用 sayHi()。这按预期解决,并打印“Hello”。这里没有什么太令人兴奋的了。现在,让我们继承 Person

作者继续解释了在 MisunderstoodPerson 类型转换为 Person 的实例上调用sayHi()

请注意, sayHi() 是在扩展中声明的,这意味着该方法将通过消息调度来调用。当调用 greetings(person:) 时,sayHi() 通过表调度被调度到 Person 对象。由于 MisunderstoodPerson 覆盖是通过消息调度添加的,因此 MisunderstoodPerson 的调度表在调度表中仍然具有 Person 实现,并且随之而来的是混乱。

我想知道作者是如何得出这样的结论的,greetings(person:) 方法使用 table dispatch 来调用 sayHi()

作者在博客前面提到的一件事是,当 NSObject 子类在初始声明中(意味着不在扩展中)声明一个方法时,将使用表调度。

所以我假设参数“person”类型是对于greetings(person:)方法的Person,调用的方法是sayHi(),它在Person类的初始声明中声明,使用表调度,sayHi()来自人叫可以肯定地说,使用了 Person 见证表

一旦我们有了MisunderstoodPerson 的子类并将这个实例传递给 greetings(person:) 这个实例应该被视为Person。我在这里有一个困惑,在这里有几个问题。

  • 该实例的类型为MisunderstoodPerson,因此此处使用MisunderstoodPerson 的见证表。
  • 或者该实例已被类型转换为Person,因此这里将使用Person 的证人表。

对于应该使用谁的见证表,作者没有在博客中澄清这一点。即使在某些情况下,我也觉得作者甚至描述了为协议创建见证表的编译器。从他在博客中分享的图像可以看出,如下所示。

在此处输入图片说明 如果有人可以解释一下,我将不胜感激。

Ham*_*ish 4

该博文有点过时了,因为继承NSObject不再改变类的分派行为(在 Swift 3 中,它会导致成员隐式暴露给 Obj-C,这会改变扩展成员的分派行为,但这是不再是这样了)。他们给出的示例也不再在 Swift 5 中编译,因为您只能覆盖dynamic扩展中的成员。

\n\n

为了区分静态调度和动态调度,让我们分别考虑协议。对于协议,如果满足以下条件,则使用动态调度:

\n\n
    \n
  • 该成员在主协议主体中声明(这称为需求或定制点)。
  • \n
  • 该成员在协议类型值P、协议组合类型值P & X或通用占位符类型值上调用T : P,例如:

    \n\n
    protocol P {\n  func foo()\n}\n\nstruct S : P {\n  func foo() {}\n}\n\nfunc bar(_ x: S) {\n  x.foo() // Statically dispatched.\n}\n\nfunc baz(_ x: P) { \n  x.foo() // Dynamically dispatched.\n}\n\nfunc qux<T : P>(_ x: T) { \n  x.foo() // Also dynamically dispatched.\n}\n
    Run Code Online (Sandbox Code Playgroud)
  • \n
\n\n

如果协议是@objc,则使用消息调度,否则使用表调度。

\n\n

对于非协议成员,您可以问这样的问题:“这可以被覆盖吗?”。如果答案是否定的,那么您正在考虑静态调度(例如struct成员或final类成员)。如果它可以被覆盖,那么您正在考虑某种形式的动态调度。然而值得注意的是,如果优化器可以证明它没有被重写(例如,如果它fileprivate在该文件中并且没有被重写),那么它可以被优化为使用静态调度。

\n\n

对于普通的方法调用,dynamic修饰符是区分动态调度的两种当前形式:表调度和 Obj-C 消息调度。如果成员是dynamic,Swift 将使用消息调度。如前所述,此规则看起来非常简单,但是某些成员没有显式标记dynamic\xe2\x80\x93 ,而是编译器推断它。这包括:

\n\n\n\n

Swift 中一种鲜为人知的方法调用形式是动态方法调用,它是通过访问值@objc的成员来完成的AnyObject。例如:

\n\n
import Foundation\n\nclass C {\n  @objc func foo() {}\n}\n\nfunc bar(_ x: AnyObject) {\n  // Message dispatch (crashing if the object doesn\'t respond to foo:).\n  x.foo!()\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

此类调用始终使用消息调度。

\n\n

我认为这总结了当前在何处使用调度机制的规则。

\n\n
\n\n
\n

一旦我们有了子类MisunderstoodPerson并将该实例传递给\n, greetings(person:)该实例就应该被视为Person. 我在这里有一个困惑,并且有几个问题。

\n\n
    \n
  • 该实例属于类型,因此此处将使用MisunderstoodPerson见证表\n 。MisunderstoodPerson
  • \n
  • 或者实例已被类型转换为,因此此处将使用Person见证表。Person
  • \n
\n
\n\n

(轻微的术语挑剔:对于类,它被称为 vtable 而不是见证表)

\n\n

它始终是与所使用的实例的动态类型相对应的 vtable,因此在本例中它将是MisunderstoodPersonvtable。

\n