应该使用谁的见证人表?

时间:2019-04-04 06:05:54

标签: swift dispatch

我想对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实例上的 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 。 我在这里很困惑,在这里有几个问题。

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

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

enter image description here 如果有人可以解释,我将非常感激。

1 个答案:

答案 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 –编译器会对其进行推断。这包括:

在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。