「这是我参与2022首次更文应战的第14天,活动概况检查:2022首次更文应战」
咱们都知道,在OC中办法的调用是经过objc_msgSend来发送消息的;那么在Swift中,办法的调用时怎么完成的呢?
办法的查找
咱们来看如下代码:
在ViewController类中,咱们新建了一个Teacher类,在其中定义了一个teach办法,在viewDidLoad办法中初始化Teacher并调用teach办法(为了避免其他调用发生的代码,咱们将super.viewDidLoad的调用删除掉),咱们在调用teach办法的时候打上断点,此刻,咱们检查汇编页面的状况:
在汇编指令中,咱们剖析办法的调用主要是经过bl和blr两个指令;咱们留意到在调用__allocating_init和swift_release两个bl指令之间还有一个blr指令,那么这个blr履行是否便是调用的teach办法呢?咱们向下履行汇编指令到blr x8:
经过指令打印,咱们确认此刻便是在调用teach办法,那么teach办法是怎么找到的呢?咱们来逐行剖析汇编代码:
-
bl 0x104012a1c:此行汇编指令调用了__allocating_init办法,返回了一个Teacher的实例目标,返回值是放在了x0寄存器中; -
mov x20, x0:将寄存器x0的值复制到x20寄存器,此刻x20寄存器寄存的便是Teacher的实例目标; -
str x20, [sp, #0x8]和str x20, [sp, #0x10]是将寄存器x20的值写入到内存中,咱们能够不做重视; -
ldr x8, [x20]:将x20寄存器中的值读取到x8寄存器中,由于是64位架构,所以此处读取的是8字节,x20中寄存的是Teacher实例目标,实例目标的第一个8字节是metadata,此刻的x8寄存器寄存的是实例目标的metadata; -
ldr x8, [x8, #0x50]:将寄存器x8中的地址偏移0x50的巨细,然后将偏移后的地址寄存进x8; -
blr x8:跳转到x8寄存器的地址,经过打印咱们能够看到此刻的x8便是teach办法;
teach办法的调用进程:先找到实例目标的metadata,经过metadata地址偏移必定的巨细,就能够找到实例目标的办法;
函数表
那么如果有多个办法呢?咱们再增加两个办法teach1和teach2个办法,然后也调用这两个办法,来看一下汇编代码:
咱们能够看到三个办法的调用,别离对应了三个blr x8,而且这三个办法的地址相差8字节,也便是一个函数指针的巨细;在内存中,这三个函数的内存地址是接连的;那么Swift中函数的调度便是根据函数表的调度;
接下来,咱们经过SIL文件来验证一下:
经过生成的SIL能够发现,Teacher的三个办法都是寄存在sil_vtable中的,他便是类的函数表;
TargetClassDescriptor
咱们已经剖析过Metadata的结构如下:
struct Metadata{
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}
在此结构中咱们需求留意这样一个typeDescriptor特点,不管是Class,Struct还是Enum都有自己的Descriptor,这是对类的详细描述;经过Swift源码能够看到其类型为TargetClassDescriptor:
经过剖析TargetClassDescriptor及其承继联系,能够剖析出其数据结构大致如下:
struct TargetClassDescriptor {
ContextDescriptorFlags Flags;
TargetRelativeContextPointer<Runtime> Parent;
TargetRelativeDirectPointer<Runtime, const char, /*nullable*/ false> Name;
TargetRelativeDirectPointer<Runtime, MetadataResponse(...),
/*Nullable*/ true> AccessFunctionPtr;
TargetRelativeDirectPointer<Runtime, const reflection::FieldDescriptor,
/*nullable*/ true> Fields;
TargetRelativeDirectPointer<Runtime, const char> SuperclassType;
uint32_t MetadataNegativeSizeInWords;
uint32_t MetadataPositiveSizeInWords;
uint32_t NumImmediateMembers;
uint32_t NumFields;
uint32_t FieldOffsetVectorOffset;
}
在其中并没有vtable相关的特点;咱们全文查找TargetClassDescriptor发现该类有一个别号:
在源码中咱们查找ClassDescriptor,咱们在源码中全文查找发现内容太多,那么咱们怎么找呢?咱们经过文件称号发现有一个GenMeta.cpp的文件,经过称号能够斗胆猜想该文件大概率是生成Metadata的,经过此处GenMeta.cpp文件的查找结果:
咱们能够定位到类ClassContextDescriptorBuilder这是当时类的描述的创立者;在该类的layout办法中咱们能够看到如下的完成:
首先咱们检查super::layout的完成:
能够看到,此处是在创立咱们之前剖析的TargetClassDescriptor的结构体,并给特点赋值;
接下来,咱们检查addVTable办法的完成:
咱们斗胆猜想,此处的B便是咱们的结构体TargetClassDescriptor,此结构体可不补全为:
struct TargetClassDescriptor {
ContextDescriptorFlags Flags;
TargetRelativeContextPointer<Runtime> Parent;
TargetRelativeDirectPointer<Runtime, const char, /*nullable*/ false> Name;
TargetRelativeDirectPointer<Runtime, MetadataResponse(...),
/*Nullable*/ true> AccessFunctionPtr;
TargetRelativeDirectPointer<Runtime, const reflection::FieldDescriptor,
/*nullable*/ true> Fields;
TargetRelativeDirectPointer<Runtime, const char> SuperclassType;
uint32_t MetadataNegativeSizeInWords;
uint32_t MetadataPositiveSizeInWords;
uint32_t NumImmediateMembers;
uint32_t NumFields;
uint32_t FieldOffsetVectorOffset;
uint32_t offset;
uint32_t size;
// 接下来是vtable
}
那么咱们如果验证上面的定论是正确的呢?下一篇文章咱们经过MachO文件来验证上述定论;











