Swift 的函数派发可以分为 静态 和 动态 两种机制,而动态派发又分为 函数表派发 和 消息派发 。
Swift 函数的派发方式和 对象类型、函数声明位置 有关。
- 值类型使用静态派发;
- 类(不管是否继承自NSObject)中定义的函数默认使用函数表派发;
- 在继承自 NSObject 的类中重写了基类的函数,比如 UIViewController 的生命周期方法,会通过消息派发调用;
类的 Extension 中声明的函数默认使用静态派发; - 协议中声明的带有默认实现的函数会使用函数表派发,协议由于特殊的 PWT 结构,存在多种派发可能;如果用 @objc + @dynamic 修饰过的函数,会通过 Objective-C 的消息派发方式进行调用。
简单来说,静态派发在编译期能确定函数的内存地址,从而直接找到对应实现;
函数表派发通过读取函数表 vtable 找到对应的函数指针;
消息派发则为 objc_msgsend 方式;这三种派发方式可查看如下链接文章进行了解(网址:https://betterprogramming.pub/a-deep-dive-into-method-dispatches-in-swift-65a8e408a7d0?gi=cac13485c979 )。
由于在调用效率上,静态派发 > 函数表派发 > 消息派发,如果能减少不必要的动态派发,运行时函数调用速度得到显著提升。
动态派发的目的是支持函数多态,如果对于不需要重写的函数,可以用 final 修饰符修饰,编译器会将函数优化为静态派发。
使用 final, private 优化派发方式
如果对象只在声明的文件中可见,可以用 private 或 fileprivate 进行修饰。编译器会对private 或 fileprivate对象进行检查,确保没有其他继承关系的情形下,自动打上final标记,进而使得对象获得静态派发的特性。
需要注意的是,单独使用 @objc 修饰函数并不会使得函数派发变成消息派发,@objc 的作用仅是将 Swift 函数暴露给 Objective-C 调用,所以 @objc 可以和 final, private 关键词一起修饰函数定义。
调用结构体方法的本质
在 OC 中,调用一个方法的本质是消息传递,底层通过 objc_msgSend 函数去查找方法并调用。而 Swift 是一门静态语言,没有运行时的机制,那原生的 Swift 方法又是如何调用的呢?
在 Swift 中,调用一个结构体的方法是直接拿到函数的地址直接调用,包括初始化方法,没有 OC 那么复杂的流程。
需要注意的是,结构体的类方法调用也和实例方法的调用一样,都是直接拿函数的地址调用。在 Swift 中声明一个类方法需要在 func 前家加上 static 关键字。
Swift 是一门静态语言,许多东西在运行的时候就可以确定了,所以才可以直接拿到函数的地址进行调用,这个调用的形式也可以称作静态派发。
网友评论