swift底层探索 05 -深入探讨swift的方法调用机制
2020-12-21 本文已影响0人
Henry________
在swift底层探索 03 - 值类型、引用类型一文中解释过值类型和引用类型的内存布局
。像这样:
1. struct-直接调用
要想知道方法是如何调用的,我是从方法是如何保存开始探索。
从[图一]看结构体没有像类
那样的继承、metaData-ISA逻辑。而内存中也找不到方法的任何踪迹,方法存在哪里呢?
1.1 struct方法调用
调用方法structfunc1
,然后打开汇编堆栈
图二
- 找到了方法的地址
0x100002bd0
并且存在代码段,但是没法找到和结构体的关系。
machOView查看
图三- 这部分内存是在程序编译链接的时候就已经生成好的。所以说
结构体方法在编译链接阶段已经确定了,并保存到符号表中
- 在
符号表中
依旧可以找到方法的指针0x100002bd0
,而且发现方法名是存在String Table
中的。所以调用前无需额外操作
1.2 struct + protocol
protocol Prot {
func protocolFunc()
}
struct structModel:Prot{
var age : Int = 18
func structfunc1(){}
func structfunc2(){}
func protocolFunc(){}
}
var str = structModel()
str.protocolFunc()
同样打开汇编调试
- 发现
协议方法与普通方法调用是一样的机制:直接调用
1.3 struct + extension情况
struct structModel{
var age : Int = 18
func structfunc1(){}
func structfunc2(){}
}
extension structModel{
func extensionFunc(){}
}
var str = structModel()
str.extensionFunc()
打开汇编调试
- 发现
拓展与普通方法调用是一样的机制:直接调用
【总结】
-
结构体的方法
无需额外空间存储,在编译链接阶段就已经确定,编译器在你调用之前就已经确定了方法的指针地址,这种方法调用称为直接调用
。
2. enum-直接调用
enum enumModel{
case `default`
func enumFunc(){}
}
enumModel.default.enumFunc()
打开汇编调试
- 与结构体相同是
直接调用
-
枚举的方法
和结构体的方法调用机制是已完全一致的。都是直接调用
。
【总结】
-
值类型
是直接调用
3. class-方法调用(函数表调用)
3.1 普通方法
借助之前的经验,先使用汇编来看一下调用堆栈。
class classtModel{
func classfunc1(){}
func classfunc2(){}
}
var cls = classtModel()
cls.classfunc1()
cls.classfunc2()
汇编调用堆栈
图五
sil文件
图六
通过图五
,图六
得出的结论:
-
不是直接静态调用
,是通过对self
进行地址偏移后找到方法指针,进行调用。 - *0x50(classfunc1) -> *0x58(classfunc12两个方法在内存里是
连续
的。
swift函数表初始化源码
通过汇编的查看知道了方法和类本身的关系的,方法是如何存储的呢?
- 在类初始化的时候将类中所有方法都放到
classWords
这个数组中,而且继承 - 类的方法调用方式:
函数表调用
。
- 代码准备
class PersonModel{
var age : Int = 18
func test(){}
func test1(){}
}
var p = PersonModel()
p.test()
p.test1()
调试验证
- 代码验证,编译并打开
汇编调试
class PersonModel{
var age : Int = 18
func test(){}
func test1(){}
}
var p = PersonModel()
p.test()
p.test1()
-
读取寄存期
-
结论
- 可以看到
class
中的方法,是以数组的结构直接存在metaData(原类)的内存里;
swift中vtable与oc中method_list区别
oc-method_list
- 在oc中
method_list
是一个二维数组包含:普通方法(包含父类方法)数组、类别方法数组.
swift-vtable
class superClass{
func superClassfunc1(){}
}
class classtModel:superClass{
func classfunc1(){}
func classfunc2(){}
}
extension classtModel{
func extensionfunc1(){}
}
- classtModel
继承
superClass;classtModel拓展
classtModel.
看下sil文件
- 在
class
中包含:父类方法
,本类方法
- 不包含:
拓展方法
3.2 class + protocol
protocol Prot {
func protocolFunc()
}
class classtModel:Prot{
func classfunc1(){}
func classfunc2(){}
func protocolFunc(){}
}
查看sil文件
- 协议方法出现在
vtable
中,代表和普通方法一致是:函数表调用
3.3 class + entension
class classtModel{
func classfunc1(){}
}
extension classtModel{
func extensionfunc1(){}
}
var cls = classtModel()
cls.extensionfunc1()
打开汇编调试
- 发现
entension
中的方法调用和值类型
的调用一致:直接调用
- 因为在类初始化的时候就已经完成
vtable的创建
,有继承关系时extension没法找到一个合理的起始位置
开发存放entension
中的方法
3.4 final、class、static关键字
方法前增加
final关键字
后,该方法不得被继承。
class classtModel{
final func classfunc1(){}
}
var cls = classtModel()
cls.classfunc1()
打开汇编调试
- 发现在
普通方法
前加上final关键字
后方法调用和值类型调用方式一致
为:直接调用
.可以当做是一个静态方法看.
注:
增加class关键字
、static关键字
后,调用方式都会变为直接调用
3.5 @objc / dynamic关键字
@objc是将该方法暴露给oc使用
dynamic关键字是将方法标记为可变方法。
以@objc为例:
class classtModel{
@objc func classfunc1(){}
dynamic func classfunc2(){}
}
查看sil文件:
- 两个方法都出现在了
vtable
中,说明这两个都放都是使用:函数表
3.6 @objc dynamic 组合关键字
@objc dynamic是将方法保留给oc且还可以动态修改
class classtModel{
@objc dynamic func classfunc2(){ }
}
var cls = classtModel()
cls.classfunc2()
- 使用
@objc dynamic
之后,相当于是和oc的方法一样可以动态修改了。所以在调用的时候也需要使用动态消息转发
【面试题】
protocol TestProtocol {
}
class CJLTeacher: TestProtocol{
var age = 18
var double = 1.85
func teach(){
print("LGTeacher teach")
}
}
func test(_ value: TestProtocol){
let valueType = type(of: value)
print(valueType)
}
var t = CJLTeacher()
let t1: TestProtocol = CJLTeacher()
test(t)
test(t1)
会输出什么?
LGTeacher teach
TestProtocol teach
- 体现了
swift的多态性
如果是这样
protocol TestProtocol {
func teach(){
print("TestProtocol teach")
}
}
输出又会变成:
LGTeacher teach
LGTeacher teach
【总结】
引用类型的方法调用相对复杂:直接调用
,函数表调用
,消息转发
都会出现。
调用方式总结
值类型 | 引用类型 | |
---|---|---|
普通方法 | 直接调用 | 函数表调用 |
protocol协议 | 直接调用 | 函数表调用 |
extension拓展 | 直接调用 | 直接调用 |
final | - | 直接调用 |
继承方法 | - | 函数表调用 |
@objc | - | 函数表调用 |
dynamic | - | 函数表调用 |
@objc dynamic | - | objc_msgSend消息转发 |
dynamic拓展
class classtModel{
dynamic func classfunc2(){
print("classfunc2")
}
}
extension classtModel{
@_dynamicReplacement(for: classfunc2)
private func repFunc(){
print("repFunc")
}
}
var cls = classtModel()
cls.classfunc2()
- 使用
@_dynamicReplacement(for: 要替换的方法名)
关键字,可以实现oc中的方法交换