目录
1. 类的普通方法调度
写一个结构体和一个类,对比看看方法调用的方式:
// 结构体 struct PersonStruct { func changClassName() {} } let s = PersonStruct() s.changClassName() // 类 class PersonClass { func changClassName() {} } let c = PersonClass() c.changClassName()
生成 SIL 代码:
【1】结构体及类的 SIL 代码:
与结构体不同的是:为PersonClass类自动生成了一个反初始化方法。
【2】执行方法的 SIL 代码:
在调用的方式中,可以看到类的方法,不是由function_ref修饰,而是class_method修饰。
【3】还有一个不同点是,SIL 中为 PersonClass 自动生成了sil_vtable:
由上面 SIL 代码,我们可以看出,SIL 为类的方法创建了 sil_vtable,并在调用时,用class_method来修饰。这样的类的方法调度,是Swift 中动态派发的一种方式,叫做函数派发。
这里由sil_vtable关键字声明的就是函数表。函数表初始化的源码如下:
从源码中看,函数表中的数据结构是一个数组,源码是以遍历的的方式去获取函数表内的函数的,所以函数表是按顺序存放类中可能是函数派发去执行的函数,但是不一定函数表内的函数都会被以函数派发的方式去调度。
2. OC 继承链中的方法列表存储结构
我们知道OC 中的方法是消息派发的方式。 每个对象中都有一个 isa 指针,指向自己的类。类中存放着该类实现的方法列表。本类方法列表中存放着本类实现的方法及父类方法列表的指针。在消息派发时,会先查找本来的方法列表,如果没找到,再去查找父类的方法列表,以此类推,来寻找方法的实现。
假设A类继承B类,B类继承C类,如下图所示:
3. Swift 继承连中的函数表存储结构
Swift 类中函数派发与消息派发类似, 所有类也会维护一个自己的函数表,不同的是所有未被复写的父类所实现的函数地址都会拷贝在这个表中, 而不是由一个指向父类方法表的指针替代,被重写的函数,在函数表中会指定为子类中的函数。由于少了一步指针寻址步骤, 在派发效率上要比基于消息的派发高效。
假设A类继承B类,B类继承C类,如下图所示:
代码验证一下:
Swift
class PersonClass: NSObject { override init() { super.init() @objc func changClassName7() {} dynamic func changClassName8() {} } } class PersonClassSub: PersonClass { func runSub() {} // 重写的函数,在函数表中会指定为子类中的函数 override func changClassName7() {} } class PersonClassSubSub: PersonClassSub { func runSubSub() {} }
到这里,证实2件事情:
- Swift的函数表是按顺序存放的
- 在类的继承关系中,函数表中存放所有的方法,由上到下,依次排列,先是父类的方法,再是子类的方法。