通过之前的分析,结构体是值类型
,类是引用类型
。那结构体和类的方法存储在哪里?我们分析一下
静态派发
值类型对象的函数的调用方式是静态调用
,即直接地址调用
,调用函数指针,这个函数指针在编译、链接完成后
,当前函数的地址就已经确定了,拿在执行代码的过程中就直接跳转到这个地址来执行当前对应的方法,存放在代码段,而结构体内部并不存放方法。因此可以直接通过地址直接调用
。
动态派发
类中声明的方法是通过V-table
来进行调度的。
V-Table
在SIL
中的表示是这样的:
//声明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
我们通过一个简单的例子来看一下
class LGTeacher {
func teach(){}
func teach2(){}
func teach3(){}
func teach4(){}
@objc deinit{}
init(){}
}
通过SIL源文件
查看其在SIL中的v-table
,如下图所示
由上图可知
sil_vtable
:关键字LGTeacher
:表明当前是LGTeacher
class的函数表- 其次就是当前方法的生命对应着方法的名称
- 本质:函数表的本质就类似于我们理解的
数组
,声明在class内部
的方法在不加任何关键字修饰的过程中,连续存放
在我们当前的地址空间
中
通过查看源码发现其内部是通过for循环编码
,然后offset+index
偏移,然后获取method,将其存入到偏移后的内存中,从这里可以印证函数是连续存放
的。
可以得出结论:对于class中函数
来说,类的方法调度是通过V-Taable
,其本质就是一个连续的内存空间(数组结构)
。
函数声明位置的不同也会导致派发方式的不同。如果我们在类的扩展中声明的函数,这里就是一个直接调用。
其原因是因为子类将父类的函数表全部继承
了,如果此时子类增加函数
,会继续在连续的地址中插入,假设extension函数也在函数表中
,则意味着子类也有,但是子类无法并没有相关的指针记录函数是父类方法还是子类方法,所以不知道方法该从哪里插入
,导致extension中的函数无法安全的放入子类中。所以在这里可以侧面证明extension中的方法是直接调用的,且只属于类,子类是无法继承的
。
开发注意点:
- 继承方法和属性,不能写extension中。
- 而extension中创建的函数,一定是只属于自己类,但是其
子类也有其访问权限
,只是不能继承和重写
。
扩展 : final、@objc、dynamic修饰函数
final修饰
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
流程,即动态消息转发
。