应该使用谁的见证表?

问题描述 投票:4回答:1

我想深入了解Swift中的方法调度。我从this popular blog上读到了三种类型的调度,如下所示:

  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'

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

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

作者继续解释对MisunderstoodPerson对人物进行类型转换的实例的调用sayHi():

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

我想知道作者如何得出结论,问候(person :)方法使用表调度来调用sayHi()

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

所以我假设参数'person'类型是问候(person :)方法的Person,并且调用的方法是sayHi(),它在Person类的初始声明中声明,使用表调度和sayHi()来自人被召唤。可以说使用人见证表是安全的。

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

  • 该实例的类型为MisunderstoodPerson,因此将在此处使用MisunderstoodPerson的见证表。
  • 或者实例已经对Person进行了类型转换,因此会在这里使用Person的表格。

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

enter image description here我真的很感激,如果有人可以解释的话。

swift dispatch
1个回答
4
投票

那篇博文有点过时了,继承自NSObject不再改变一个类的调度行为(在Swift 3中,它会导致成员被隐式暴露给Obj-C,这将改变扩展成员的调度行为,but this is no longer the case) 。他们提供的示例也不再在Swift 5中编译,因为您只能覆盖扩展中的dynamic成员。

为了区分静态调度和动态调度,让我们分别考虑协议。对于协议,如果两者都使用动态分派:

  • 该成员在主协议体内声明(这称为需求或定制点)。
  • 该成员在协议类型值P,协议组合类型值P & X或通用占位符类型值T : P上调用,例如: protocol P { func foo() } struct S : P { func foo() {} } func bar(_ x: S) { x.foo() // Statically dispatched. } func baz(_ x: P) { x.foo() // Dynamically dispatched. } func qux<T : P>(_ x: T) { x.foo() // Also dynamically dispatched. }

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

对于非协议成员,您可以问一个问题:“这可以覆盖吗?”。如果答案是否定的,那么您正在查看静态调度(例如,struct成员或final类成员)。如果它可以被覆盖,那么你正在寻找某种形式的动态调度。但是值得注意的是,如果优化器可以证明它没有被覆盖(例如,如果它是fileprivate而不是在该文件中覆盖),则可以优化它以使用静态分派。

对于普通的方法调用,dynamic修饰符区分了当前形式的动态调度,表分派和Obj-C消息调度。如果成员是dynamic,Swift将使用消息调度。如上所述,这条规则似乎非常简单,但是有些成员没有明确标记为dynamic - 编译器推断它。这包括:

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

import Foundation

class C {
  @objc func foo() {}
}

func bar(_ x: AnyObject) {
  // Message dispatch (crashing if the object doesn't respond to foo:).
  x.foo!()
}

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

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


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

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

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

它始终是vtable,它对应于所使用的实例的动态类型,所以在这种情况下它将是MisunderstoodPerson的vtable。

© www.soinside.com 2019 - 2024. All rights reserved.