「这是我参与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
文件来验证上述定论;