方法调度 - ShenYj/ShenYj.github.io GitHub Wiki
Swift的方法调度分为静态调度和动态调度。
值类型为一般为静态调度,而引用类型、协议类型默认情况在类型主体中一般采用表调度,在扩展中采用静态调度。
Object-C
的分类方法会插入到类的方法表中, 而Swift
的extension
方法是静态调用(免去了Object-C
插入扩展方法的异步操作, 理解为空间换取时间)
Swift
继承, 子类会复制一份父类的方法到自己的方法表中,然后再编译自身的方法至方法表
同时还会受到修饰符final
、class
、dynamic
、@objc
等因素影响而改变调度方式。比如swift类实例方法在类型主体中使用@objc
修饰时,依然采用表调度方式,但是在扩展中使用@objc
修饰会采用消息调度方式。
静态调度也被称为直接调度(Direct Dispatch),它是最快且最高效的一种方法调度类型。在编译阶段,编译器就已经知道了所有被静态调度的方法在内存中的地址,因而在运行阶段,这些方法可以被立即执行。
对于动态调度的方法,编译器不知道它的内存地址,所以无法对代码进行优化,可能导致一些性能问题的出现。动态调度可以为覆写父类中的方法提供支持,使得Swift也可以实现多态。 动态调度又可分为现两种:
-
表调度
表调度是编译语言中最常用的方法之一,用于实现这种动态行为。 在编译时,为每个类构造一个的vtable,其中包含与每个类中的实现相对应的功能指针数组。 与静态调度相比,表调度需要两条额外的指令(读和跳转)来确定在运行时运行的方法。 对于平凡类型来说,并没有什么虚函数指针,所以加入了The Protocol Witness Table (PWT) 函数表用于协议类型方法的调度。
-
消息调度
消息调度是最动态但也是最慢的调度技术。 为了找到调度消息背后的方法,运行时需要爬整个类层次结构以确定要调用的方法。 但是,这也意味着可以在运行时更改程序的行为-这使诸如swizzing之类的技术能够起作用。 Objective-C在很大程度上依赖于消息调度,并且还通过Objective-C运行时向Swift提供了该功能。
方法调度方式与方法声明的位置、引用类型、修饰符、推断优化修饰等相关。
在 Swift 中默认情况下:
类型主体 | extension | |
---|---|---|
class | 表 | 静态 |
struct、enum | 静态 | 静态 |
protocol | 表 | 静态 |
关键字修饰后对方法调度产生的影响,可以参考这篇笔记: Swift 5.x 关键字: final、dynamic、objc和_dynamicReplacement
自由函数和那些在结构体上调用的方法是静态派发 (statically dispatched) 的。对于这些函数的调用,在编译的时候就已经确定了。对于静态派发的调用,编译器可能会实施 内联 (inline) 优化,也就是说,完全不去做函数调用,而是将函数调用替换为函数中需要执行的代码。优化器还能够帮助丢弃或者简化那些在编译时就能确定不会被实际执行的代码。
类或者协议上的方法可能是动态派发 (dynamically dispatched) 的。编译器在编译时不需要知道哪个函数将被调用。在 Swift 中,这种动态特性要么由 vtable 来完成,要么通过 selector 和 objc_msgSend 来完成,前者的处理方式和 Java 或是 C++ 中类似,而后者只针对那些用 @objc 修饰的类和协议上的方法。