前言:
上篇文章分析了Swift
中的指针
,以及指针
在Swift
中的基本使用
,这篇文章主要分析方法
在Swift
中如何调用
的,在swift中方法调度分为两种:
-
静态调⽤(直接调用)
-
查找调用(函数都按照顺序存储在
vtable
中,按照地址偏移方式进行调用)
静态调⽤
对于值类型对象的函数的调用方式是静态(直接)派发(调用),也可以说是直接地址调用(指针调用),也就是说,在编译,链接完成后当前函数的地址在Mach-O
代码段就已经有了确定的位置。
Swift
中典型的值类型就是结构体,那么我们就通过如下的分析,证明结构体中函数的调用是静态调用。
案例,在Xcode中创建:
struct LGTeacher {
func teach() {}
}
var t = LGTeacher()
t.teach()
print("end")
通过汇编看函数的调用
在汇编的函数调用中,可以看到callq
直接调用的是0x100002c10
地址,所以由此可看出Swift结构体
的方法调用时直接地址派发
的
Mach-O验证
在Mach-O
中查看方法是否在__text
段:
由上图可看出,方法的存储地址就在__text段
中,所以可验证函数指针
在编译、链接完成后就已经确定
了,存放在代码段
符号表Symbol Table验证
-
查看
Functions Starts
中所有的方法的重命名,命名规则:工程名+类名+函数名
-
查看符号表的地址偏移量
函数地址:0000000100002C10
,和汇编
中的地址
一致
- 查看Sting Table表
- 用终端查看重命名符合表中
s12NewSwiftDemo9LGTeacherV5teachyyF
符号
- 还原符号
动态派发
既然有静态派发
,那么当然就会有动态派发
,在Swift
中的引用类型
数据代表类
中的函数调用
就是动态派发
知识补充
- 汇编指令补充
- blr:带返回的跳转指令,跳转到指令后边跟随寄存器中保存的地址
- mov:将某一寄存器的值复制到另一寄存器(只能用于寄存器与起存起或者 寄存器与常量之间 传值,不能用于内存地址,mov x1,x0 将寄存器x0的值复制到寄存器x1中)
- ldr:将内存中的值读取到寄存器中(ldr x0,[x1,x2] 将寄存器x1和寄存器x2 相加作为地址,取该内存地址的值翻入寄存器x0中)
- str:将寄存器中的值写入到内存中(str x0, [x0, x8] 将寄存器x0的值保存到内存[x0 + x8]处)
- bl:跳转到某地址
- vtable 简介
Swift
中的vtable
在SIL
文件中的格式:
//声明sil vtable关键字
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
以LGTacher为例,其SIL
中的v-table
如下所示:
class LGTeacher{
func teach(){}
func teach2(){}
func teach3(){}
func teach4(){}
@objc deinit{}
init(){}
}
函数表
可以理解为 数组
,声明在 class内部的方法在不加任何关键字修饰的过程中,是连续存放
在我们当前的地址空间中的,汇编断点调试:
由上图可了解到函数表
的首地址是一片连续
的内存地址
源码分析
上面由汇编的方式探究了vtable的内存存储方式,说明在vtable中肯定有其创建的过程,打开swift源码,全局搜索:
其内部是通过for循环
编码,然后offset+index
偏移,然后获取method
,将其存入到偏移后的内存中,从这里可以印证函数是连续存放的
,所以对于class中函数来说,类的方法调度
是通过V-Taable
,其本质
就是一个连续的内存空间
(数组结构)
extension 中的方法
创建LGTeacher的extension
class LGTeacher{
func teach(){}
func teach1(){}
func teach2(){}
func teach3(){}
func teach4(){}
}
extension LGTeacher {
func teach5(){}
}
var t = LGTeacher()
t.teach5()
查看SIL文件中的VTable方法,看是否在编译过程中存入到函数表中
由上图可看出,extension
的方法在编译时并未存储到vtable
表中,那extension
中的方法是如何调用的呢?查看汇编代码
:
由上图可看出,extension直接callq
的是0x100002b80
函数地址,属于静态调用
.
final、@objc、dynamic修饰函数
创建LGTeacher
的final,@objc,dynamic
class LGTeacher{
final func teach(){}
func teach1(){}
@objc func teach2(){}
func teach3(){}
dynamic func teach4(){}
}
extension LGTeacher {
func teach5(){}
}
var t = LGTeacher()
t.teach()
t.teach2()
t.teach4()
t.teach5()
- final
查看SIL文件
可看出final修饰的函数并不在vtable中,查看汇编调用:
final关键字修饰的函数直接callq
的是0x100002b80
函数地址,属于静态调用
.
- @bjc
通过sil
文件我们可以发现,使用@objc
修饰的方法依旧存在VTable
中,特别提醒:
对于使用@objc
修饰的方法,实际上是生成了两个方法,其中一个就是我们Swift
中原有的方法,另一个就是如上面代码所示的@objc
方法,并在其内部调用了Swift
原有的方法。所以使用@objc
修饰的方法本质是,通过sil
代码中的@objc
方法调用,Swift
中的方法来实现的。
- dynamic
sil
文件我们可以看到,使用dynamic
修饰的方法是存放在VTable
中的,目前没看到和swif中的方法有什么区别,官网中的定义为具有动态性.和@_dynamicReplacement
关键字一起使用可以进行方法交换
- @objc + dynamic
汇编断点调试:
可以看出走的是objc_msgSend
流程,即 动态消息转发
总结
-
Swift
中的方法调用分为静态调度
和动态调度
两种 -
值类型(例如
struct
)中的方法就是静态调度
-
引用类型中的方法就是
动态调度,
其中函数的调度是通过V-Table函数表
来进行调度的,即动态调度
-
extension
和final
中的函数调度方式是静态调度
-
@objc
修饰的函数调度方式是函数表调度
,如果OC中需要使用,class还必须继承NSObject
-
dynamic
修饰的函数的调度方式是函数表调度
,使函数具有动态性
-
使用
dynamic
和@objc
同时修饰的方法的调度方式是objc_msgSend
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!