常用汇编指令
在学习函数派发之前,我们先简单了解一下arm64的一些汇编指令
- mov:将某一寄存器的值复制到另一寄存器
//将寄存器x0的值复制到寄存器x1中
mov x1, x0
- add:将某一寄存器的值和另一寄存器的值相加,并将结果保存在另一个寄存器
//将寄存器x1和x2的值相加后保存到寄存器x0中
add x0,x1,x2
- sub:将某一寄存器的值和另一寄存器的值相减,并将结果保存在另一个寄存器
//将寄存器x1和x2的值相减后保存到寄存器x0中
sub x0,x1,x2
- str:将寄存器中的值写入到内存中
//将寄存器x0中的值保存到栈内存[x0 + x8]处
str x0, [x0,x8]
- ldr:将内存中的值读取到寄存器中
//将寄存器x1和寄存器x2的值相加作为地址,取该内存地址的值放入到寄存器x0中
ldr x0,[x1,x2]
- blr:跳转到某地址(无返回) (函数无返回值)
- bl:跳转到某地址(有返回) (函数有返回值)
函数的返回值一般是放在x0寄存器中的
函数调用分析
class
class Teacher{
func test(){
print("test")
}
func test2(){
print("test2")
}
}
let teacher = Teacher()
teacher.test()
teacher.test2()
汇编分析teacher.test()
通过上面的分析,我们可以得出test函数的调用过程:
找到metadata,确定函数地址偏移量(metadata + 偏移量),执行函数
SIL分析teacher.test()
sil_vtable 是每个类的函数表
struct
struct StructPerson{
func test(){
print("test")
}
}
汇编分析
也可以通过sil分析,struct中,并未生成sil_vtable
Enum
enum EnumStudent{
case A,B
func test(){
switch self{
case .A:
print("A")
case .B:
print("B")
}
}
}
汇编分析
总结
影响函数派发方式(针对的都是class类型)
- extension静态派发
class Teacher{
func test(){
print("test")
}
}
extension Teacher{
func test2(){
print("test2")
}
}
let t = Teacher()
t.test()
t.test2()
我们知道t.test()的函数调用函数表调用,但是test2()呢?我们通过汇编得到如下:
由上面汇编代码可以看出,extension里面的test2()函数成了直接调用,
也就是说extension 不会影响类的vtable结构,extension里面的方法使用静态派发的方式
- 添加了final关键字的函数无法被重写,使用静态派发的方式,不会出现在vtable中
class Teacher{
final func test(){
print("test")
}
}
let t = Teacher()
t.test()
final的使用:在开发中,当我们的类,属性,方法不需要被重载时,可以添加final关键字
- @objc dynamic 消息派发的方式
class Teacher{
@objc dynamic func test(){
print("test")
}
}
let t = Teacher()
t.test()
- dynamic: 函数均可添加 dynamic 关键字,为非objc类和值类型的函数赋予动态性,但派发方式还是函数表派发。
- @objc: 该关键字可以将Swift函数暴露给Objc运行时,依旧是函数表派发。