gpt4 book ai didi

ios - Swift 中会使用哪种调度方法?

转载 作者:搜寻专家 更新时间:2023-10-31 22:01:20 25 4
gpt4 key购买 nike

在 WWDC Understanding Swift Performance ,当对象的类型是协议(protocol)时声明:调用协议(protocol)所需的函数将使用 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()

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

  1. 是否将扩展的默认实现复制到 MyClass 的 v 表,并通过 MyClass 的 v 表分派(dispatch)方法?

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

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

最佳答案

在这个例子中:

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() 实现,因此它可以直接分派(dispatch)到扩展方法。

所以回答你的个人问题:

1) Is it the extension's default implementation copied to MyClass's v-table, and method being dispatched through MyClass's v-table?

否——Swift 类 v-table 仅包含类声明主体中定义的方法。也就是说:

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) Is it still use Existential container to dispatch methods, and the Existential container's PWT contains extension's default implementation?

代码中没有存在的容器:

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

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

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

MyClass 实例被封装在一个存在的容器中,该容器带有一个协议(protocol)见证表,该表将对 testFuncA() 的调用映射到扩展方法(现在我们正在处理动态 dispatch )。


查看上述所有操作的一个好方法是查看编译器生成的 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() -> () 的引用,这是协议(protocol)扩展中的方法。因此 Swift 使用的是静态分派(dispatch)。

现在让我们看看当我们这样调用时会发生什么:

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用于查找为 MyClass 实例调用协议(protocol)要求 MyProtocol.testFuncA 的函数。这将检查协议(protocol)见证表以了解 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 查找之后调用查找的 witness 函数:

  %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的使用,我们可以得出结论,这里使用了动态协议(protocol)调度。

我们在这里简单介绍了很多技术细节;随时使用 the documentation 逐行完成 SIL找出每条指令的作用。我很乐意澄清您可能不确定的任何事情。

关于ios - Swift 中会使用哪种调度方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48422621/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com