动态派发也能够称之为表派发或者运行时派发,动态派发的要害界说:派发的函数是在运行时动态选择的。表分配从名字来看并不那么明显,但经过指针表这个完成细节能够看出:表派发也是基于指针表来动态派发的。 Swift 实践上完成了两种类型的表派发:用于类层次结构的虚拟表(virtual tables)和用于协议的见证表(witness tables)。
虚表派发
代码示例如下:
class Cat {
func cry() {
print("Meow")
}
func eat() {
print("Purr")
}
}
final class Lion: Cat {
override func cry() {
print("Roar")
}
}
因为咱们的 .swift
文件是独立编译的,Swift 编译器无法确定在哪里运用哪个完成,这些信息仅在运行时可用。 所以在对象实践创立之前,咱们不知道咱们正在处理的是 Cat 仍是 Lion。
虚拟表其实也没有什么神奇的当地。它只是一个在每个子类的编译时创立的列表,它将每个函数映射到它们在内存中的完成。假如 Lion 重写了 cry()
,则该表指向Lion.cry()
上界说的指令。假如它不重写 eat()
,那么 Lion 的虚拟表将指向 Cat 父类中界说的指令。
// Cat 的虚表
| 函数名 | 指针地址 |
|-------|-----------|
| cry() | 0x1000 |
| eat() | 0x1008 |
// Lion 的虚表
| 函数名 | 指针地址 |
|-------|-------- |
| cry() | 0x2000 [重写] |
| eat() | 0x1008 [承继]|
表派发比直接派发更慢。因为在运行时,假如要动态分配到函数,Swift 进行以下几个过程:
协议的见证表
协议能够让开发者经过组合的方式为目标类型增加多态性,乃至能够为结构或枚举等值类型增加多态性。协议办法经过协议见证表派发。
它们的机制与虚拟表相同:契合协议的类型包括元数据(存储在 Existential containers 中),其间包括指向其见证表的指针,该见证表本身便是一个函数指针表。
当在协议类型上执行函数时,Swift 检查 Existential containers
,查找见证表,并派发到要执行的函数的内存地址。
一般,当咱们运用依靠项注入时,咱们只指定协议咱们的依靠项契合的接口而没有详细类型。其他时分,咱们可能有一个 Collection,其间包括咱们想要迭代的各种契合协议的对象。在这些情况下,办法派发是经过见证表进行的。
虚表和见证表的 SIL
如前所述,在 Swift 中调用动态派发有两种主要办法。首要,让咱们看一下虚表派发:
class Incrementer {
func increment(_ int: Int) -> Int {
return int + 1
}
func deincrement(_ int: Int) -> Int {
return int - 1
}
}
class DoubleIncrementer: Incrementer {
override func increment(_ int: Int) -> Int {
return int + 2
}
}
let threePlusTwo = DoubleIncrementer().increment(3)
咱们能够看到在 SIL 中创立的虚表(vtable)。DoubleIncrementer 完成了这两个办法,但只覆盖了一个指针,指向自己的完成:
sil_vtable Incrementer {
#Incrementer.increment: (Incrementer) -> (Int) -> Int : @$s4main11IncrementerC9incrementyS2iF // Incrementer.increment(_:)
#Incrementer.deincrement: (Incrementer) -> (Int) -> Int : @$s4main11IncrementerC11deincrementyS2iF // Incrementer.deincrement(_:)
}
sil_vtable DoubleIncrementer {
#Incrementer.increment: (Incrementer) -> (Int) -> Int : @$s4main17DoubleIncrementerC9incrementyS2iF [override] // DoubleIncrementer.increment(_:)
#Incrementer.deincrement: (Incrementer) -> (Int) -> Int : @$s4main11IncrementerC11deincrementyS2iF [inherited] // Incrementer.deincrement(_:)
}
让咱们看看当咱们运用协议时它是什么样子的:
protocol Incrementer {
func increment(_ int: Int) -> Int
}
struct IncrementerImpl: Incrementer {
func increment(_ int: Int) -> Int {
return int + 1
}
}
let fourPlusOne = IncrementerImpl().increment(4)
这一次,咱们在 SIL 中看到一个见证表(sil_witness_table):
sil_witness_table hidden IncrementerImpl: Incrementer module main {
method #Incrementer.increment: <Self where Self : Incrementer> (Self) -> (Int) -> Int : @$s4main15IncrementerImplVAA0B0A2aDP9incrementyS2iFTW // protocol witness for Incrementer.increment(_:) in conformance IncrementerImpl
}
在这两个非常简单的示例中,编译器实践上最终将 main()
函数静态地直接派发给increment()
的办法完成,而绕过分配表。假如你进行了优化编译,Swift 会完全抛弃这些函数,并在调用时发生预先计算好的结果!