Swift中将使用哪种调度方法?

时间:2018-01-24 12:34:36

标签: ios swift

在WWDC Understanding Swift Performance中,当对象的类型是协议时它声明:调用协议所需的函数将使用Existential容器来调度方法。

protocol MyProtocol {
    func testFuncA()
}

extension MyProtocol {
    func testFuncA() {
        print("MyProtocol's testFuncA")
    }
}

class MyClass: MyProtocol {}

// This use Existential Container, find implementation through PWT.
let object: MyProtocol = MyClass()
object.testFuncA()

这就是我的问题:当对象被指定为 MyClass 时,Swift如何找到实现?我对这个问题有两个解释。

  1. 是否将扩展程序的默认实施方式复制到 MyClass 的v-table,并通过 MyClass &#调度方法39; s v-table?

  2. 是否仍然使用Existential容器来调度方法,而Existential容器的PWT包含扩展的默认实现?

  3. // Use Dynamic Dispatch or Static Dispatch? How?
    let object: MyClass = MyClass()
    object.testFuncA()
    

3 个答案:

答案 0 :(得分:5)

在这个例子中:

protocol MyProtocol {
    func testFuncA()
}

extension MyProtocol {
    func testFuncA() {
        print("MyProtocol's testFuncA")
    }
}

class MyClass : MyProtocol {}

let object: MyClass = MyClass()
object.testFuncA()
使用

静态调度object的具体类型在编译时是已知的;它是MyClass。然后,Swift可以看到它符合MyProtocol而没有提供自己的testFuncA()实现,因此它可以直接调度到扩展方法。

所以回答你的个人问题:

  

1)扩展名的默认实现是否已复制到MyClass   v-table和通过MyClass的v-table调度的方法?

否 - Swift类v表只保存在类声明主体中定义的方法。也就是说:

protocol MyProtocol {
  func testFuncA()
}

extension MyProtocol {
  // No entry in MyClass' Swift v-table.
  // (but an entry in MyClass' protocol witness table for conformance to MyProtocol)
  func testFuncA() {
    print("MyProtocol's testFuncA")
  }
}

class MyClass : MyProtocol {
  // An entry in MyClass' Swift v-table.
  func foo() {}
}

extension MyClass {
  // No entry in MyClass' Swift v-table (this is why you can't override
  // extension methods without using Obj-C message dispatch).
  func bar() {}
}
  

2)是否仍然使用Existential容器来调度方法,以及   存在容器的PWT包含扩展名的默认值   执行?

代码中没有存在容器:

let object: MyClass = MyClass()
object.testFuncA()

存在容器用于协议类型实例,例如您的第一个示例:

let object: MyProtocol = MyClass()
object.testFuncA()

MyClass实例装在一个存在容器中,其中包含一个协议见证表,用于将对testFuncA()的调用映射到扩展方法(现在我们正在处理动态调度)。

查看上述所有操作的好方法是查看编译器生成的SIL;这是生成代码的一个相当高级别的中间表示(但是低级别足以看到正在发挥什么样的调度机制)。

您可以通过运行以下命令来执行此操作(请注意,最好先从程序中删除print语句,因为它们会大大增加生成的SIL 的大小):

swiftc -emit-sil main.swift | xcrun swift-demangle > main.silgen  

让我们来看看这个答案中第一个例子的SIL。这是main函数,它是程序的入口点:

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @main.object : main.MyClass       // id: %2
  %3 = global_addr @main.object : main.MyClass : $*MyClass // users: %9, %7

  // function_ref MyClass.__allocating_init()
  %4 = function_ref @main.MyClass.__allocating_init() -> main.MyClass : $@convention(method) (@thick MyClass.Type) -> @owned MyClass // user: %6
  %5 = metatype $@thick MyClass.Type              // user: %6
  %6 = apply %4(%5) : $@convention(method) (@thick MyClass.Type) -> @owned MyClass // user: %7
  store %6 to %3 : $*MyClass                      // id: %7

  // Get a reference to the extension method and call it (static dispatch).
  // function_ref MyProtocol.testFuncA()
  %8 = function_ref @(extension in main):main.MyProtocol.testFuncA() -> () : $@convention(method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // user: %12
  %9 = load %3 : $*MyClass                        // user: %11
  %10 = alloc_stack $MyClass                      // users: %11, %13, %12
  store %9 to %10 : $*MyClass                     // id: %11
  %12 = apply %8<MyClass>(%10) : $@convention(method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> ()
  dealloc_stack %10 : $*MyClass                   // id: %13


  %14 = integer_literal $Builtin.Int32, 0         // user: %15
  %15 = struct $Int32 (%14 : $Builtin.Int32)      // user: %16
  return %15 : $Int32                             // id: %16
} // end sil function 'main'

我们在这里感兴趣的是这一行:

  %8 = function_ref @(extension in main):main.MyProtocol.testFuncA() -> () : $@convention(method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // user: %12

function_ref指令获取对编译时已知的函数的引用。您可以看到它正在获取函数@(extension in main):main.MyProtocol.testFuncA() -> ()的引用,这是协议扩展中的方法。因此Swift正在使用静态调度。

现在让我们来看看当我们这样打电话时会发生什么:

let object: MyProtocol = MyClass()
object.testFuncA()

main函数现在看起来像这样:

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @main.object : main.MyProtocol  // id: %2
  %3 = global_addr @main.object : main.MyProtocol : $*MyProtocol // users: %9, %4
  // Create an opaque existential container and get its address (%4).
  %4 = init_existential_addr %3 : $*MyProtocol, $MyClass // user: %8
  // function_ref MyClass.__allocating_init()
  %5 = function_ref @main.MyClass.__allocating_init() -> main.MyClass : $@convention(method) (@thick MyClass.Type) -> @owned MyClass // user: %7
  %6 = metatype $@thick MyClass.Type              // user: %7
  %7 = apply %5(%6) : $@convention(method) (@thick MyClass.Type) -> @owned MyClass // user: %8

  // Store the MyClass instance in the existential container.
  store %7 to %4 : $*MyClass                      // id: %8

  // Open the existential container to get a pointer to the MyClass instance.
  %9 = open_existential_addr immutable_access %3 : $*MyProtocol to $*@opened("F199B87A-06BA-11E8-A29C-DCA9047B1400") MyProtocol // users: %11, %11, %10

 // Dynamically lookup the function to call for the testFuncA requirement. 
  %10 = witness_method $@opened("F199B87A-06BA-11E8-A29C-DCA9047B1400") MyProtocol, #MyProtocol.testFuncA!1 : <Self where Self : MyProtocol> (Self) -> () -> (), %9 : $*@opened("F199B87A-06BA-11E8-A29C-DCA9047B1400") MyProtocol : $@convention(witness_method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %9; user: %11

  // Call the function we looked-up for the testFuncA requirement.
  %11 = apply %10<@opened("F199B87A-06BA-11E8-A29C-DCA9047B1400") MyProtocol>(%9) : $@convention(witness_method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %9


  %12 = integer_literal $Builtin.Int32, 0         // user: %13
  %13 = struct $Int32 (%12 : $Builtin.Int32)      // user: %14
  return %13 : $Int32                             // id: %14
} // end sil function 'main'

这里有一些关键的区别。

使用init_existential_addr创建(不透明)存在容器,并将MyClass实例存储到其中(store %7 to %4)。

然后使用open_existential_addr 打开打开 ,它会获得指向存储的实例的指针(MyClass实例)。

然后,witness_method用于查找函数以调用MyProtocol.testFuncA实例的协议要求MyClass。这将检查协议见证表中MyClass的一致性,该表列在生成的SIL的底部:

sil_witness_table hidden MyClass: MyProtocol module main {
  method #MyProtocol.testFuncA!1: <Self where Self : MyProtocol> (Self) -> () -> () : @protocol witness for main.MyProtocol.testFuncA() -> () in conformance main.MyClass : main.MyProtocol in main // protocol witness for MyProtocol.testFuncA() in conformance MyClass
}

这列出了函数@protocol witness for main.MyProtocol.testFuncA() -> ()。我们可以检查这个函数的实现:

// protocol witness for MyProtocol.testFuncA() in conformance MyClass
sil private [transparent] [thunk] @protocol witness for main.MyProtocol.testFuncA() -> () in conformance main.MyClass : main.MyProtocol in main : $@convention(witness_method) (@in_guaranteed MyClass) -> () {
// %0                                             // user: %2
bb0(%0 : $*MyClass):
  %1 = alloc_stack $MyClass                       // users: %7, %6, %4, %2
  copy_addr %0 to [initialization] %1 : $*MyClass // id: %2

  // Get a reference to the extension method and call it.
  // function_ref MyProtocol.testFuncA()
  %3 = function_ref @(extension in main):main.MyProtocol.testFuncA() -> () : $@convention(method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // user: %4
  %4 = apply %3<MyClass>(%1) : $@convention(method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> ()


  %5 = tuple ()                                   // user: %8
  destroy_addr %1 : $*MyClass                     // id: %6
  dealloc_stack %1 : $*MyClass                    // id: %7
  return %5 : $()                                 // id: %8
} // end sil function 'protocol witness for main.MyProtocol.testFuncA() -> () in conformance main.MyClass : main.MyProtocol in main'

果然,它获得了function_ref扩展方法,并调用了该函数。

然后在使用以下行进行witness_method查找后调用查找的见证函数:

  %11 = apply %10<@opened("F199B87A-06BA-11E8-A29C-DCA9047B1400") MyProtocol>(%9) : $@convention(witness_method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %9

因此,我们可以得出结论,此处使用动态协议调度,基于witness_method的使用。

我们在这里轻松了解了很多技术细节;随意使用the documentation查找每条指令的作用,逐行完成SIL。我很乐意澄清你可能不确定的任何事情。

答案 1 :(得分:2)

这是一个棘手的问题,因为我们正在讨论编译器实现的细节,并且可以使用每个新版本的Swift更改它们(因此任何知识都可能会很快过时)。

说到Swift 3,我前段时间遇到过文章:that。它实际上告诉我们

  

Swift有两个可以声明方法的位置:在   类型的初始声明,以及扩展名。取决于   声明类型,这将改变调度的执行方式。

key-values
     

在上面的例子中,mainMethod将使用表分派,和   extensionMethod将使用直接调度

它还包含表格: Breno Henrique

根据此表,协议扩展中声明的方法(所谓的默认实现)始终直接调度

我无法确定,但我相信Swift 4中可能会出现同样的行为。

答案 2 :(得分:0)

类MyClass比较您的协议MyProtocol。所以你的对象知道协议实现。就这样。或者我不明白这个问题?