返回

多角度体会 Swift 方法派发

IOS

导言

作为一门现代且备受推崇的编程语言,Swift 以其强大、安全和高效的特性闻名。Swift 拥有的一个关键特性就是多态性,它允许我们以统一的方式处理不同类型的对象。这种多态性的实现是通过方法派发机制来实现的,该机制允许在运行时动态地确定要调用的方法。

在本文中,我们将深入探讨 Swift 中的三种方法派发方式:静态派发(直接派发)、VTable 派发(函数表派发)和消息派发。我们将从底层 SIL 中间语言和汇编代码的角度来剖析这些派发机制,以便获得更深入的理解。

静态派发

静态派发是一种最简单的派发方式,它发生在编译时,方法调用被解析到一个特定的函数指针上。当方法属于一个类,且在编译时就知道该类的确切类型时,就会发生这种情况。

在 SIL 中,静态派发表示为 function_ref 指令,该指令直接跳转到方法的实现上。在汇编中,这通常表现为一条直接跳转指令(例如 jmp)。

// SIL
function_ref @swift_allocObject(%swift.type* @objc_class) -> @owned %swift.refcounted*

// 汇编
jmpq *(%rip)  // 直接跳转到 allocObject 实现

静态派发的一个优势是它的高效性,因为它避免了在运行时进行任何动态查找。然而,它的局限性在于它只能用于已知类型的对象。

VTable 派发

VTable 派发(也称为函数表派发)是一种动态派发方式,它发生在运行时,方法调用被解析到一个函数指针表(称为 VTable)上。VTable 中的每个条目都对应于该类中可用的方法。

当对象属于一个类层次结构中未知的子类时,就会发生 VTable 派发。在编译时,编译器会生成一个指向 VTable 的指针,该指针存储在对象中。在运行时,方法调用会被解析到 VTable 中的相应条目,然后跳转到该方法的实现上。

在 SIL 中,VTable 派发表示为 class_method 指令,该指令间接跳转到 VTable 中的函数指针上。在汇编中,这通常表现为一条间接调用指令(例如 callq)。

// SIL
class_method %swift.method %0 : $Class, #Class.method!getter.1
                 : $@convention(method) (@guaranteed Class) -> () -> ()

// 汇编
callq *(%rax)  // 间接调用 VTable 中的 method 方法

VTable 派发的优势在于它的灵活性,因为它允许在运行时调用未知类型的对象的方法。然而,它的缺点是它的开销略高于静态派发,因为它涉及额外的间接查找。

消息派发

消息派发是一种用于 Objective-C 对象的方法派发方式,它已被 Swift 采用。消息派发与 VTable 派发类似,但它使用了一个消息发送机制,该机制在运行时将消息发送给对象。

当对象是一个 Objective-C 对象或一个 Swift 对象(继承自 Objective-C 对象)时,就会发生消息派发。消息由一个选择器和一组参数组成,选择器是一个标识方法的字符串。

在 SIL 中,消息派发表示为 message_send 指令,该指令间接跳转到一个消息发送函数上。在汇编中,这通常表现为一条消息发送指令(例如 objc_msgSend)。

// SIL
message_send %1 : $Class, #selector(Class.method), %0
                 : $@convention(objc_method) (Class, Selector) -> ()

// 汇编
movq %rdi, %rax  // 将接收者对象传递到 rax
movq %rsi, %rdi  // 将选择器传递到 rdi
callq objc_msgSend  // 发送消息

消息派发的一个优势是它提供了 Objective-C 互操作性,并且可以用于调用动态库中的方法。然而,它的开销比 VTable 派发更高,因为它涉及额外的消息发送机制。

结论

Swift 的方法派发机制提供了多种选择,以适应不同的性能和灵活性要求。静态派发是最有效率的,但仅适用于已知类型的对象。VTable 派发提供了一个折衷,它允许动态派发,但开销略高于静态派发。消息派发是最灵活的,但也是开销最大的。

通过了解这些方法派发机制,我们可以更好地理解 Swift 的多态性是如何实现的,并做出明智的决策,选择最适合我们特定需求的派发方式。