我想深入了解 Swift 中的方法调度。我读自 this popular blog关于三种 dispatch 方式如下:
- 动态
- 表(Swift 中的见证表)
- 留言
在那篇博客中,作者说 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() 的推理:
The greetings(person:) method uses table dispatch to invoke sayHi(). This resolves as expected, and “Hello” is printed. Nothing too exciting here. Now, let’s subclass Person
作者继续解释了在 MisunderstoodPerson 类型转换为 Person 的实例上调用 sayHi():
Notice that sayHi() is declared in an extension meaning that the method will be invoked with message dispatch. When greetings(person:) is invoked, sayHi()is dispatched to the Person object via table dispatch. Since the MisunderstoodPerson override was added via message dispatch, the dispatch table for MisunderstoodPerson still has the Person implementation in the dispatch table, and confusion ensues.
我想知道作者是如何得出greetings(person:)方法使用table dispatch调用sayHi()的结论
作者在博客前面提到的一件事是,当 NSObject 子类在初始声明中声明一个方法时(意思是不在扩展中),将使用表分派(dispatch)。
所以我假设对于 greetings(person:) 方法,参数 'person' 类型是 Person 并且调用的方法是 sayHi() 声明在Person 类的初始声明使用表调度并调用 Person 的 sayHi()。 可以肯定地说使用了 Person witness 表。
一旦我们有了子类 MisunderstoodPerson 并将这个实例传递给 greetings(person:) 这个实例应该被视为 Person。 我在这里很困惑,在这里有几个问题。
- 实例的类型是MisunderstoodPerson 所以会见证 此处使用 MisunderstoodPerson 表。
- 或者实例已被类型转换为Person,因此将在此处使用Person 的见证表。
关于应该使用谁的见证表,作者在博客中没有明确说明。甚至在某些情况下,我感觉作者甚至描述了编译器为协议(protocol)创建见证表。从他在博客中分享的图片可以看出,如下所示。
最佳答案
那篇博文有点过时,因为从 NSObject
继承不再改变类的调度行为(在 Swift 3 中,它会导致成员隐式暴露给 Obj-C,这会改变扩展成员的调度行为,but this is no longer the case)。他们给出的示例也不再在 Swift 5 中编译,因为您只能覆盖扩展中的 dynamic
成员。
为了区分静态派发和动态派发,让我们分别考虑协议(protocol)。对于协议(protocol),如果两者都使用动态调度:
- 成员在主要协议(protocol)主体中声明(这称为要求或自定义点)。
成员在协议(protocol)类型值
P
、协议(protocol)组合类型值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. }
如果协议(protocol)是@objc
,则使用消息调度,否则使用表调度。
对于非协议(protocol)成员,您可以提出以下问题:“这可以被覆盖吗?”。如果答案是否定的,那么您正在查看静态分派(dispatch)(例如 struct
成员或 final
类成员)。如果它可以被覆盖,那么您正在寻找某种形式的动态调度。然而,值得注意的是,如果优化器可以证明它没有被覆盖(例如,如果它是 fileprivate
并且在该文件中没有被覆盖),那么它可以被优化为使用静态调度。
对于普通的方法调用,dynamic
修饰符区分当前两种形式的动态调度,表调度和 Obj-C 消息调度。如果成员是动态的
,Swift 将使用消息调度。如前所述,这条规则看起来非常简单,但是一些成员没有明确标记为 dynamic
——编译器会推断它。这包括:
- 从 Objective-C 导入的成员。
- 覆盖
动态
成员。 NSManaged
属性。- Non-final
@objc
class extension members .
Swift 中一种鲜为人知的方法调用形式是动态方法调用,它是通过访问 AnyObject
值上的 @objc
成员来完成的。例如:
import Foundation
class C {
@objc func foo() {}
}
func bar(_ x: AnyObject) {
// Message dispatch (crashing if the object doesn't respond to foo:).
x.foo!()
}
此类调用始终使用消息调度。
我认为这总结了当前关于在何处使用调度机制的规则。
Once we have subclass
MisunderstoodPerson
and pass this instance togreetings(person:)
this instance should be treated asPerson
. I have a confusion here and have couple of questions here.
- The instance is of type
MisunderstoodPerson
so would witness table ofMisunderstoodPerson
be used here.- Or the instance has been typecast to
Person
, so would witness table ofPerson
be used here.
(有点挑剔的术语:对于类,它被称为虚表而不是见证表)
它始终是与所用实例的动态类型相对应的 vtable,因此在这种情况下,它将是 MisunderstoodPerson
的 vtable。
关于swift - 应该使用谁的见证表?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55508918/