在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如何找到实现?我对这个问题有两个解释。
是否将扩展程序的默认实施方式复制到 MyClass 的v-table,并通过 MyClass &#调度方法39; s v-table?
是否仍然使用Existential容器来调度方法,而Existential容器的PWT包含扩展的默认实现?
// Use Dynamic Dispatch or Static Dispatch? How?
let object: MyClass = MyClass()
object.testFuncA()
答案 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。所以你的对象知道协议实现。就这样。或者我不明白这个问题?