我正在参与「启航方案」
OC办法的底层调用过程大家都很清楚了,但Swift并不存在Runtime,因而本文首要剖析Swift中的类和结构体的办法存储在哪里,以及怎么调用的
首要内容
- 静态派发
- 动态派发
1. 静态派发
值类型对象的函数的调用办法是静态调用,即直接地址调用,调用函数指针。这个函数指针在编译、链接完结后就现已确认了,寄存在代码段。
结构体属于值类型,因而结构体内部并不寄存办法。能够直接经过地址直接调用。
1.1 检查
1.1.1、调试
阐明:
- 检查能够发现结构体中函数的调用,是直接经过地址来调用
- 这种经过地址直接调用的办法便是静态派发
- 那么这个函数地址是存储在哪里的呢
1.1.2、Mach-O检查
阐明:
- 这个地址是存储在Mach-0中的__text,也便是代码段中
- 需求履行的汇编指令都在这儿
1.1.3、符号检查
关于上面的剖析,还有个疑问:直接地址调用后边是符号,这个符号哪里来的?
符号
阐明:
- 在静态调用中,会看到关于这个地址的符号
- 地址咱们现已知道是存储在了__text中
- 那么这个符号存储在哪里呢,检查符号表
符号表:
阐明:
- 符号能够经过符号表来查找
- 可是符号表中并不存储字符串
- 详细的字符串会直接存储到字符串表中
- 符号表存储的是相应字符串在字符串表中的地址
- 然后根据符号表中的偏移值到字符串中查找对应的字符
- 此时会进行命名重整,工程名+类名+函数名
字符串表:
阐明:
- 字符串表,寄存了所有的变量名和函数名,以字符串方法存储
- 由于在符号表中函数名偏移了两个字节,因而这儿前两个字节存储的 便是该函数名
留意:
*假如在release下,是不会存储符号的,直接存储静态链接的地址
- 一旦编译完结,其地址确认后,当时的符号表就会删去当时函数对应的符号
- 在release环境下,符号表中存储的只是不能确认地址的符号
- 关于不能确认地址的符号,是在运行时确认的,即函数第一次调用时(相当于懒加载)
1.2 函数符号命名规矩
1.2.1 C函数
关于C函数来说,命名的重整规矩便是在函数名之前加_。因而C中不允许函数重载,由于在底层的函数符号没有办法区别
1.2.2 OC函数
OC函数的符号命名规矩是-[类名 函数名]。因而也是不能够重载的,由于假如有重载的函数,重载是参数和回来值的差异,而在底层符号无法区别
1.2.3 Swift函数
Swift的命名规矩更加杂乱,会带有参数和回来的差异,因而是能够确保函数符号的唯一性,也便是能够进行函数重载了
2. 动态派发
2.1 函数表(V_Table)的知道
在SIL文件中的格局:
//声明silvtable关键字
decl::=sil-vtable
//silvtable中包含关键字、标识(即类名)、所有的办法
2sil-vtable::='sil_vtable'identifier'{'sil-vtable-entry*'}'
//办法中包含了声明以及函数名称
3sil-vtable-entry::=sil-decl-ref':'sil-linkage?sil-function-na
me
代码:
SIL中V_Table:
阐明:
- sil_vtable:关键字,表明vtable
- WYTeacher表明某个类的函数表
- 接下来是办法定义
- init办法和deinit办法
- 办法按次序存储d奥函数表中
2.2 函数表的理解
函数表用来存储类中的办法,存储办法类似于数组,办法接连寄存在函数表中。
检查办法地址:
阐明:
- 观察这几个办法的偏移地址,能够发现办法是接连寄存的,偏移8个字节
- 正好对应V-Table函数表中的排放次序,便是按照定义次序排放在函数表中
2.3 函数表源码探究
在源码中检查函数表的详细完成,经过initClassVTable来初始化类的函数表。
源码:
阐明:
- initClassVTable便是用来创立一个类的V_Table表的办法
- 能够看到其内部是经过for循环编码,然后offset+index偏移拿到Method地址
- 之后将办法地址存入到偏移后的内存中,从这儿能够印证函数是接连寄存的
2.4 扩展中的函数怎么调度
给一个类添加extension,子类承继自该类,那么子类会承继这个extension中的办法吗
代码:
extension WYTeacher {
func teach5() {
print("teach5")
}
}
class WYStudent: WYTeacher {
func teach6() {
print("teach6")
}
}
SIL文件
阐明:
- 能够看到子类并没有承继扩展中的办法
- 子类只承继了函数表中的函数
- 这是由于子类有父类办法和子类办法,假如扩展中的办法刺进到子类的函数表中,此时无法区别往哪里插
- extension中的办法是直接调用的,且只属于类,子类是无法承继的
2.5 特殊润饰符的函数
2.5.1 final
final 润饰的办法是 直接调度的,能够经过SIL验证
代码:
/*
3、final
*/
class WYTeacher {
final func teach(){ print("teach") }
func teach2(){ print("teach2") }
func teach3(){ print("teach3") }
func teach4(){ print("teach4") }
@objc deinit{}
init(){}
}
SIL检查:
阐明:
- 明显看到在sil_vtable中没有存储teach1
- 打断点也能够检查到teach是直接调度
- 因而final润饰的函数没有存储在函数表中
2.5.2 @objc
运用@objc关键字是将swift中的办法露出给OC,@objc润饰办法的调度办法是函数表调度。
代码:
/*
4、@objc
*/
class WYTeacher{
@objc func teach(){ print("teach") }
func teach2(){ print("teach2") }
func teach3(){ print("teach3") }
func teach4(){ print("teach4") }
@objc deinit{}
init(){}
}
SIL文件:
阐明:
- 能够看到在函数表中存储有teach办法
- 而且断点调试中也是有teach的
2.5.3 dynamic
dynamic的意思是能够动态修正,意味着当类承继自NSObject时,能够运用method-swizzling
代码:
SIL文件:
阐明:
- 其中teach函数的调度仍是 函数表调度
@objc + dynamic的完成: @objc + dynamic能够完成音讯发送
阐明:
- 能够看到在底层运用objc_msgSend来发送音讯调用
- 也简单理解,这儿其实是以OC的办法调用方法
办法交换完成:
阐明:
- 能够看到teach和teach5现已发生了交换
- 只要经过@_dynamicReplacement,将当时办法teach5和参数中teach进行转化
2.6 总结
留意:
- 承继办法和属性,不能写extension中。
- 而extension中创立的函数,一定是只属于自己类,可是其子类也有其拜访权限,只是不能承继和重写
- OC拜访Swift,会生成OC和Swift的两种办法
- swift原有的函数
- @objc符号露出给OC来运用的函数: 内部调用swift的
- @objc+@dynamic又能够动态,又能够露出给OC,这样才能够运用音讯转发
总结:
- 关于class中函数来说,类的办法调度是经过V-Taable,其本质便是一个接连的内存空间(数组结构)。
- extension中的办法是直接调用的,且只属于类,子类是无法承继的
- final润饰的函数调度办法是直接调度
- @objc润饰的函数调度办法是函数表调度,假如OC中需求运用,class还必须承继NSObject
- dynamic润饰的函数的调度办法是函数表调度,使函数具有动态性
- @objc + dynamic 组合润饰的函数调度,是履行的是objc_msgSend流程,即 动态音讯转发