我正在参与「启航方案」

OC办法的底层调用过程大家都很清楚了,但Swift并不存在Runtime,因而本文首要剖析Swift中的类和结构体的办法存储在哪里,以及怎么调用的

首要内容

  1. 静态派发
  2. 动态派发

1. 静态派发

值类型对象的函数的调用办法是静态调用,即直接地址调用,调用函数指针。这个函数指针在编译、链接完结后就现已确认了,寄存在代码段。

结构体属于值类型,因而结构体内部并不寄存办法。能够直接经过地址直接调用。

1.1 检查

1.1.1、调试

Swift底层探索(四)Swift函数调用过程的探索

阐明:

  • 检查能够发现结构体中函数的调用,是直接经过地址来调用
  • 这种经过地址直接调用的办法便是静态派发
  • 那么这个函数地址是存储在哪里的呢

1.1.2、Mach-O检查

Swift底层探索(四)Swift函数调用过程的探索

阐明:

  • 这个地址是存储在Mach-0中的__text,也便是代码段中
  • 需求履行的汇编指令都在这儿

1.1.3、符号检查

关于上面的剖析,还有个疑问:直接地址调用后边是符号,这个符号哪里来的?

符号

Swift底层探索(四)Swift函数调用过程的探索

阐明:

  • 在静态调用中,会看到关于这个地址的符号
  • 地址咱们现已知道是存储在了__text中
  • 那么这个符号存储在哪里呢,检查符号表

符号表:

Swift底层探索(四)Swift函数调用过程的探索

阐明:

  • 符号能够经过符号表来查找
  • 可是符号表中并不存储字符串
  • 详细的字符串会直接存储到字符串表中
  • 符号表存储的是相应字符串在字符串表中的地址
  • 然后根据符号表中的偏移值到字符串中查找对应的字符
  • 此时会进行命名重整,工程名+类名+函数名

字符串表:

Swift底层探索(四)Swift函数调用过程的探索

阐明:

  • 字符串表,寄存了所有的变量名和函数名,以字符串方法存储
  • 由于在符号表中函数名偏移了两个字节,因而这儿前两个字节存储的 便是该函数名

留意:

*假如在release下,是不会存储符号的,直接存储静态链接的地址

  • 一旦编译完结,其地址确认后,当时的符号表就会删去当时函数对应的符号
  • 在release环境下,符号表中存储的只是不能确认地址的符号
  • 关于不能确认地址的符号,是在运行时确认的,即函数第一次调用时(相当于懒加载)

1.2 函数符号命名规矩

1.2.1 C函数

关于C函数来说,命名的重整规矩便是在函数名之前加_。因而C中不允许函数重载,由于在底层的函数符号没有办法区别

Swift底层探索(四)Swift函数调用过程的探索

1.2.2 OC函数

OC函数的符号命名规矩是-[类名 函数名]。因而也是不能够重载的,由于假如有重载的函数,重载是参数和回来值的差异,而在底层符号无法区别

Swift底层探索(四)Swift函数调用过程的探索

1.2.3 Swift函数

Swift的命名规矩更加杂乱,会带有参数和回来的差异,因而是能够确保函数符号的唯一性,也便是能够进行函数重载了

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

代码:

Swift底层探索(四)Swift函数调用过程的探索

SIL中V_Table:

Swift底层探索(四)Swift函数调用过程的探索

阐明:

  • sil_vtable:关键字,表明vtable
  • WYTeacher表明某个类的函数表
  • 接下来是办法定义
  • init办法和deinit办法
  • 办法按次序存储d奥函数表中

2.2 函数表的理解

函数表用来存储类中的办法,存储办法类似于数组,办法接连寄存在函数表中。

检查办法地址:

Swift底层探索(四)Swift函数调用过程的探索

阐明:

  • 观察这几个办法的偏移地址,能够发现办法是接连寄存的,偏移8个字节
  • 正好对应V-Table函数表中的排放次序,便是按照定义次序排放在函数表中

2.3 函数表源码探究

在源码中检查函数表的详细完成,经过initClassVTable来初始化类的函数表。

源码:

Swift底层探索(四)Swift函数调用过程的探索

阐明:

  • 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文件

Swift底层探索(四)Swift函数调用过程的探索

阐明:

  • 能够看到子类并没有承继扩展中的办法
  • 子类只承继了函数表中的函数
    • 这是由于子类有父类办法和子类办法,假如扩展中的办法刺进到子类的函数表中,此时无法区别往哪里插
    • 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检查:

Swift底层探索(四)Swift函数调用过程的探索

阐明:

  • 明显看到在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文件:

Swift底层探索(四)Swift函数调用过程的探索

阐明:

  • 能够看到在函数表中存储有teach办法
  • 而且断点调试中也是有teach的

2.5.3 dynamic

dynamic的意思是能够动态修正,意味着当类承继自NSObject时,能够运用method-swizzling

代码:

Swift底层探索(四)Swift函数调用过程的探索

SIL文件:

Swift底层探索(四)Swift函数调用过程的探索

阐明:

  • 其中teach函数的调度仍是 函数表调度

@objc + dynamic的完成: @objc + dynamic能够完成音讯发送

Swift底层探索(四)Swift函数调用过程的探索

阐明:

  • 能够看到在底层运用objc_msgSend来发送音讯调用
  • 也简单理解,这儿其实是以OC的办法调用方法

办法交换完成:

Swift底层探索(四)Swift函数调用过程的探索

阐明:

  • 能够看到teach和teach5现已发生了交换
  • 只要经过@_dynamicReplacement,将当时办法teach5和参数中teach进行转化

2.6 总结

留意:

  1. 承继办法和属性,不能写extension中。
  2. 而extension中创立的函数,一定是只属于自己类,可是其子类也有其拜访权限,只是不能承继和重写
  3. OC拜访Swift,会生成OC和Swift的两种办法
    1. swift原有的函数
    2. @objc符号露出给OC来运用的函数: 内部调用swift的
  4. @objc+@dynamic又能够动态,又能够露出给OC,这样才能够运用音讯转发

总结:

  1. 关于class中函数来说,类的办法调度是经过V-Taable,其本质便是一个接连的内存空间(数组结构)。
  2. extension中的办法是直接调用的,且只属于类,子类是无法承继的
  3. final润饰的函数调度办法是直接调度
  4. @objc润饰的函数调度办法是函数表调度,假如OC中需求运用,class还必须承继NSObject
  5. dynamic润饰的函数的调度办法是函数表调度,使函数具有动态性
  6. @objc + dynamic 组合润饰的函数调度,是履行的是objc_msgSend流程,即 动态音讯转发