Swift进阶杂谈4:方法调度

经过以前的分析,结构体是值类型,类是引用类型。那结构体和类的方法存储在哪里?咱们分析一下swift

静态派发

值类型对象的函数的调用方式是静态调用,即直接地址调用,调用函数指针,这个函数指针在编译、连接完成后,当前函数的地址就已经肯定了,拿在执行代码的过程当中就直接跳转到这个地址来执行当前对应的方法,存放在代码段,而结构体内部并不存放方法。所以能够直接经过地址直接调用数组

动态派发

类中声明的方法是经过V-table来进行调度的。 V-TableSIL中的表示是这样的:安全

//声明sil vtable关键字
1 decl ::= sil-vtable
//sil vtable中包含 关键字、标识(即类名)、全部的方法
2 sil-vtable ::= 'sil_vtable' identifier '{' sil-vtable-entry* '}'
//方法中包含了声明以及函数名称
3 sil-vtable-entry ::= sil-decl-ref ':' sil-linkage? sil-function-na
me
复制代码

咱们经过一个简单的例子来看一下markdown

class LGTeacher {
    func teach(){}
    func teach2(){}
    func teach3(){}
    func teach4(){}
    @objc deinit{}
    init(){}
}
复制代码

经过SIL源文件查看其在SIL中的v-table,以下图所示 ide

由上图可知函数

  • sil_vtable:关键字
  • LGTeacher:代表当前是LGTeacher class的函数表
  • 其次就是当前方法的生命对应着方法的名称
  • 本质:函数表的本质就相似于咱们理解的数组,声明在class内部的方法在不加任何关键字修饰的过程当中,连续存放在咱们当前的地址空间

经过查看源码发现其内部是经过for循环编码,而后offset+index偏移,而后获取method,将其存入到偏移后的内存中,从这里能够印证函数是连续存放的。编码

能够得出结论:对于class中函数来讲,类的方法调度是经过V-Taable,其本质就是一个连续的内存空间(数组结构)spa

函数声明位置的不一样也会致使派发方式的不一样。若是咱们在类的扩展中声明的函数,这里就是一个直接调用。

其缘由是由于子类将父类的函数表所有继承了,若是此时子类增长函数,会继续在连续的地址中插入,假设extension函数也在函数表中,则意味着子类也有,可是子类没法并无相关的指针记录函数是父类方法仍是子类方法,因此不知道方法该从哪里插入,致使extension中的函数没法安全的放入子类中。因此在这里能够侧面证实extension中的方法是直接调用的,且只属于类,子类是没法继承的指针

开发注意点:

  • 继承方法和属性,不能写extension中。
  • 而extension中建立的函数,必定是只属于本身类,可是其子类也有其访问权限,只是不能继承和重写

扩展 : final、@objc、dynamic修饰函数

final修饰code

  • final修饰的方法是直接调度

@objc修饰

  • 使用@objc关键字是将swift中的方法暴露给OC,@objc修饰的方法是函数表调度
  • 若是只是经过@objc修饰函数,OC仍是没法调用swift方法的,所以若是想要OC访问swift,class须要继承NSObject
  • 查看SIL文件发现被@objc修饰的函数声明有两个:swift+OC,即在SIL文件中生成了两个方法
    • swift原有的函数
    • @objc标记暴露给OC来使用的函数:内部调用swift的

dynamic修饰 使用dynamic的意思是能够动态修改,觉得着当继承自NSObject时,可使用method-swizzling

场景:swift中实现方法交换 在swift中的须要交换的函数前,使用dynamic修饰,而后经过:@_dynamicReplacement(for: 函数符号)进行交换,以下所示

class PDTeacher: NSObject {
    dynamic func teach(){ print("teach") }
    func teach2(){ print("teach2") }
    func teach3(){ print("teach3") }
    func teach4(){ print("teach4") }
    @objc deinit{}
    override init(){}
}

extension PDTeacher{
    @_dynamicReplacement(for: teach)
    func teach5(){
        print("teach5")
    }
}
复制代码

teach方法替换成了teach5

总结

  • struct值类型,其中函数的调度属于直接调用地址,即静态调度
  • class引用类型,其中函数的调度是经过V-Table函数表来进行调度的,即动态调度
  • extension中的函数调度方式是直接调度
  • final修饰的函数调度方式是直接调度
  • @objc随时的函数调度方式是函数表调度,若是OC中须要使用,class还必须继承NSObject
  • dynamic修饰的函数的调度方式是函数表调度,是函数具备动态性。
  • @objc + dynamic组合修饰的函数调度,是执行的是objc_msgSend流程,即动态消息转发
相关文章
相关标签/搜索