我想对Swift中的方法分派有深入的了解。我从this popular blog中了解了以下三种调度类型:
在该博客中,作者说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实例上的 call sayHi()的推理:
greetings(person :)方法使用表分派来调用sayHi()。 这将按预期解决,并打印“ Hello”。没什么 这里令人兴奋。现在,让我们将Person子类
作者继续解释了在类型化为Person的MisunderstoodPerson实例上调用 sayHi():
请注意,sayHi()是在扩展名中声明的,这意味着 方法将与消息分派一起调用。问候时(人:) 被调用,sayHi()通过表分配给Person对象 调度。由于MisunderstoodPerson替代是通过消息添加的 派遣,MisunderstoodPerson的派遣表仍具有 派遣表中的人员实施情况,随之而来的混乱情况。
我想知道作者如何得出 greetings(person :)方法使用表分派来调用sayHi()
的结论作者在博客前面提到的一件事是,当NSObject子类在初始声明中声明一个方法(意味着不在扩展名中)时,将使用表分派。
因此,我假设 greetings(person:)方法的参数'person'类型为 Person ,并且调用的方法是sayHi(),该方法在 Person 类的初始声明使用表分派,并调用Person中的sayHi()。 可以肯定地说使用了Person见证人表。
一旦我们有了 MisunderstoodPerson 的子类,并将此实例传递给Greetings(person :),则该实例应被视为 Person 。 我在这里很困惑,在这里有几个问题。
作者没有在博客中澄清应使用谁的见证人表。即使在某些情况下,我也觉得作者甚至描述了为协议创建见证表的编译器。从他在博客中分享的图片可以明显看出这一点。
答案 0 :(得分: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
–编译器会对其进行推断。这包括:
dynamic
个成员。NSManaged
属性。@objc
class extension members。在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。