「这是我参与2022首次更文应战的第14天,活动概况检查:2022首次更文应战」

咱们都知道,在OC中办法的调用是经过objc_msgSend来发送消息的;那么在Swift中,办法的调用时怎么完成的呢?

办法的查找

咱们来看如下代码:

Swift(六)-方法调度(上)

ViewController类中,咱们新建了一个Teacher类,在其中定义了一个teach办法,在viewDidLoad办法中初始化Teacher并调用teach办法(为了避免其他调用发生的代码,咱们将super.viewDidLoad的调用删除掉),咱们在调用teach办法的时候打上断点,此刻,咱们检查汇编页面的状况:

Swift(六)-方法调度(上)

在汇编指令中,咱们剖析办法的调用主要是经过blblr两个指令;咱们留意到在调用__allocating_initswift_release两个bl指令之间还有一个blr指令,那么这个blr履行是否便是调用的teach办法呢?咱们向下履行汇编指令到blr x8

Swift(六)-方法调度(上)

经过指令打印,咱们确认此刻便是在调用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地址偏移必定的巨细,就能够找到实例目标的办法;

函数表

那么如果有多个办法呢?咱们再增加两个办法teach1teach2个办法,然后也调用这两个办法,来看一下汇编代码:

Swift(六)-方法调度(上)

咱们能够看到三个办法的调用,别离对应了三个blr x8,而且这三个办法的地址相差8字节,也便是一个函数指针的巨细;在内存中,这三个函数的内存地址是接连的;那么Swift中函数的调度便是根据函数表的调度;

接下来,咱们经过SIL文件来验证一下:

Swift(六)-方法调度(上)

经过生成的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特点,不管是ClassStruct还是Enum都有自己的Descriptor,这是对类的详细描述;经过Swift源码能够看到其类型为TargetClassDescriptor

Swift(六)-方法调度(上)

经过剖析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发现该类有一个别号:

Swift(六)-方法调度(上)

在源码中咱们查找ClassDescriptor,咱们在源码中全文查找发现内容太多,那么咱们怎么找呢?咱们经过文件称号发现有一个GenMeta.cpp的文件,经过称号能够斗胆猜想该文件大概率是生成Metadata的,经过此处GenMeta.cpp文件的查找结果:

Swift(六)-方法调度(上)

咱们能够定位到类ClassContextDescriptorBuilder这是当时类的描述的创立者;在该类的layout办法中咱们能够看到如下的完成:

Swift(六)-方法调度(上)

首先咱们检查super::layout的完成:

Swift(六)-方法调度(上)

能够看到,此处是在创立咱们之前剖析的TargetClassDescriptor的结构体,并给特点赋值; 接下来,咱们检查addVTable办法的完成:

Swift(六)-方法调度(上)

咱们斗胆猜想,此处的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文件来验证上述定论;