swift - 应该使用谁的见证表?

标签 swift dispatch

我想深入了解 Swift 中的方法调度。我读自 this popular blog关于三种 dispatch 方式如下:

  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() 的推理:

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)创建见证表。从他在博客中分享的图片可以看出,如下所示。

enter image description here 如果有人能解释一下,我将不胜感激。

最佳答案

那篇博文有点过时,因为从 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——编译器会推断它。这包括:

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 to greetings(person:) this instance should be treated as Person. I have a confusion here and have couple of questions here.

  • The instance is of type MisunderstoodPerson so would witness table of MisunderstoodPerson be used here.
  • Or the instance has been typecast to Person, so would witness table of Person be used here.

(有点挑剔的术语:对于类,它被称为虚表而不是见证表)

它始终是与所用实例的动态类型相对应的 vtable,因此在这种情况下,它将是 MisunderstoodPerson 的 vtable。

关于swift - 应该使用谁的见证表?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55508918/

相关文章:

ruby - 如何以编程方式确定哪个类/模块定义了被调用的方法?

raku - 别名为常量时无法解析签名

reactjs - Redux - reducer 与 Action 的关系

ios - UIWebView 不适合设备屏幕

ios - 在 Swift 中创建一个 JSON 对象

ios - Twitter 搜索 Api 1.1 错误 : Could not cast value of type '__NSCFDictionary' to 'NSArray'

具有继承的 C# 方法重载

ios - Firebase 实时数据库获取值(value)和增量

ios - 从保留图标的后退按钮中删除文本

android - KEYCODE_POWER 仅在长按时调度