Swift底層原理-方法調(diào)度

Swift底層原理-方法調(diào)度

  • 我們知道,在OC中方法的調(diào)用是通過objc_msgSend來(lái)發(fā)送消息的;那么在Swift中,方法的調(diào)用時(shí)如何實(shí)現(xiàn)的呢?
  • 而且在swift中不僅僅只有類可以定義方法,結(jié)構(gòu)體也可以定義方法。下面就讓我們分別研究一下他們的方法調(diào)用

結(jié)構(gòu)體方法

  • 我們通過匯編來(lái)查看一下,調(diào)用結(jié)構(gòu)體的方法時(shí),是如何調(diào)用的
struct Test {
    func test() {
        
    }
    
    func test1() {
        
    }
}

let test = Test()
test.test()
test.test1()
  • 打下斷點(diǎn),進(jìn)入?yún)R編代碼:
image
  • 可以發(fā)現(xiàn),在Swift中,調(diào)用一個(gè)結(jié)構(gòu)體的方法是直接拿到函數(shù)的地址直接調(diào)用,包括初始化方法。
  • Swift是一門靜態(tài)語(yǔ)言,許多東西在編譯器就已經(jīng)確定了,所以才可以直接拿到函數(shù)的地址進(jìn)行調(diào)用,這個(gè)調(diào)用的形式也可以稱作靜態(tài)派發(fā)
  • 這個(gè)函數(shù)地址在編譯器決定,并存儲(chǔ)在__text段中,也就是代碼段中

extension中方法調(diào)用

  • Test添加一個(gè)extension,創(chuàng)建一個(gè)test3方法:
extension Test {
    func test3() {
        
    }
}
  • 通過匯編查看
-
  • structextension的方法依然是直接調(diào)用(靜態(tài)派發(fā))

類方法

  • 前面我們已經(jīng)了解了Swift結(jié)構(gòu)體的方法調(diào)用,那么Swift的類呢
class Test {
    func test() {
        
    }
    
    func test1() {
        
    }
}

let test = Test()
test.test()
test.test1()
  • 開啟匯編調(diào)試
image
  • 在看匯編代碼前,可以簡(jiǎn)單的認(rèn)識(shí)幾個(gè)匯編指令,就可以大致了解以上匯編內(nèi)容

    • mov:將某一寄存器的值復(fù)制到另一寄存器(只能用于寄存器與寄存器或者寄存器與常量之間傳值,不能用于內(nèi)存地址),如
    mov x1, x0    將寄存器x0的值符知道寄存器x1中
    
    • ldr:將內(nèi)存中的值讀取到寄存器中,如:
    ldr x0, [x1, x2] 將寄存器x1和寄存器x2的值相加作為地址,取改地址的值放入寄存器x0中
    
    • bl、blr:跳轉(zhuǎn)到某地址(有返回)
    • x代表寄存器,x0用來(lái)存放函數(shù)計(jì)算結(jié)果
  • 在第8行,mov x20, x0,x0里面存放的是test對(duì)象

  • 在第14行,ldr x8, [x20],把test對(duì)象首地址,也就是metedata放在x8中。

  • 然后通過對(duì)metedate的偏移,拿到函數(shù)的地址。

  • 總結(jié):swift中函數(shù)調(diào)用分為了3個(gè)步驟

    1. 找到metadata
    2. 確定函數(shù)地址(metadata + 偏移量)
    3. 執(zhí)行函數(shù)
  • 那么這些函數(shù)地址存放在哪里呢?

函數(shù)表

  • 我們生成sil文件,看一下編譯時(shí)做了哪些操作

  • 來(lái)到sil文件底部

sil_vtable Test {
  #Test.test: (Test) -> () -> () : @$s4main4TestC4testyyF   // Test.test()
  #Test.test1: (Test) -> () -> () : @$s4main4TestC5test1yyF // Test.test1()
  #Test.init!allocator: (Test.Type) -> () -> Test : @$s4main4TestCACycfC    // Test.__allocating_init()
  #Test.deinit!deallocator: @$s4main4TestCfD    // Test.__deallocating_deinit
}
  • 通過sil可以發(fā)現(xiàn),Test的三個(gè)方法都是存放在sil_vtable中的,他就是類的函數(shù)表;

  • 函數(shù)表用來(lái)存儲(chǔ)類中的方法,存儲(chǔ)方式類似于數(shù)組,方法連續(xù)存放在函數(shù)表中。

函數(shù)表在類中位置

  • 在上一篇文章結(jié)構(gòu)體與類中,我們把Swift類的本質(zhì)挖掘出來(lái)了,它里面有一個(gè) 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
}
  • 在此結(jié)構(gòu)中我們需要注意這樣一個(gè)typeDescriptor屬性,不管是ClassStruct還是Enum都有自己的Descriptor
  • 我們從源碼中找到Description定義,發(fā)現(xiàn)它是TargetClassDescriptor 類型的類
template <typename Runtime>
class TargetClassDescriptor final
    : public TargetTypeContextDescriptor<Runtime>,
      public TrailingGenericContextObjects<TargetClassDescriptor<Runtime>,
                              TargetTypeGenericContextDescriptorHeader,
                              /*additional trailing objects:*/
                              TargetResilientSuperclass<Runtime>,
                              TargetForeignMetadataInitialization<Runtime>,
                              TargetSingletonMetadataInitialization<Runtime>,
                              TargetVTableDescriptorHeader<Runtime>,
                              TargetMethodDescriptor<Runtime>,
                              TargetOverrideTableHeader<Runtime>,
                              TargetMethodOverrideDescriptor<Runtime>,
                              TargetObjCResilientClassStubInfo<Runtime>> {
    // 省略具體實(shí)現(xiàn)
}
  • 根據(jù)繼承關(guān)系慢慢對(duì)比,對(duì)比出來(lái)的結(jié)果,TargetClassDescriptor里面的屬性如下
class 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相關(guān)的屬性,我們想法是找到這個(gè)類的初始化方法,里面肯定有關(guān)于屬性的初始化流程。然后找到ClassContextDescriptorBuilder這樣一個(gè)類,內(nèi)容的描述建立者,這個(gè)類就是創(chuàng)建 Descriptor 的類。
class ClassContextDescriptorBuilder
    : public TypeContextDescriptorBuilderBase<ClassContextDescriptorBuilder,
                                              ClassDecl>,
      public SILVTableVisitor<ClassContextDescriptorBuilder>
  {
    using super = TypeContextDescriptorBuilderBase;
  
    ClassDecl *getType() {
      return cast<ClassDecl>(Type);
    }

    // Non-null unless the type is foreign.
    ClassMetadataLayout *MetadataLayout = nullptr;

    Optional<TypeEntityReference> ResilientSuperClassRef;

    SILVTable *VTable;
    bool Resilient;

    SmallVector<SILDeclRef, 8> VTableEntries;
    SmallVector<std::pair<SILDeclRef, SILDeclRef>, 8> OverrideTableEntries;

  public:
    ClassContextDescriptorBuilder(IRGenModule &IGM, ClassDecl *Type,
                                  RequireMetadata_t requireMetadata)
      : super(IGM, Type, requireMetadata),
        VTable(IGM.getSILModule().lookUpVTable(getType())),
        Resilient(IGM.hasResilientMetadata(Type, ResilienceExpansion::Minimal)) {

      if (getType()->isForeign()) return;

      MetadataLayout = &IGM.getClassMetadataLayout(Type);

      if (auto superclassDecl = getType()->getSuperclassDecl()) {
        if (MetadataLayout && MetadataLayout->hasResilientSuperclass())
          ResilientSuperClassRef = IGM.getTypeEntityReference(superclassDecl);
      }

      addVTableEntries(getType());
    }

    void addMethod(SILDeclRef fn) {
      VTableEntries.push_back(fn);
    }

    void addMethodOverride(SILDeclRef baseRef, SILDeclRef declRef) {
      OverrideTableEntries.emplace_back(baseRef, declRef);
    }

    void layout() {
      super::layout();
      addVTable();
      addOverrideTable();
      addObjCResilientClassStubInfo();
    }
          
    // 省略部分方法
}
  • 在類中找到 layout 這個(gè)方法:
void layout() {
    super::layout();
    addVTable();
    addOverrideTable();
    addObjCResilientClassStubInfo();
}
  • 在這里調(diào)用了addVTable方法
void addVTable() {
    if (VTableEntries.empty())
        return;

    // Only emit a method lookup function if the class is resilient
    // and has a non-empty vtable.
    if (IGM.hasResilientMetadata(getType(), ResilienceExpansion::Minimal))
        IGM.emitMethodLookupFunction(getType());

    auto offset = MetadataLayout->hasResilientSuperclass()
        ? MetadataLayout->getRelativeVTableOffset()
        : MetadataLayout->getStaticVTableOffset();
    B.addInt32(offset / IGM.getPointerSize());
    B.addInt32(VTableEntries.size());

    for (auto fn : VTableEntries)
        emitMethodDescriptor(fn);
}
  • 在該函數(shù)中,首先拿到當(dāng)前descriptor的內(nèi)存偏移,這個(gè)偏移量是 TargetClassDescriptor 這個(gè)結(jié)構(gòu)中的成員變量所有內(nèi)存大小之和,并且在最后還拿到了 VTableEntries.size()。
  • 然后在這個(gè)偏移位置開始添加方法。
  • 總結(jié):虛函數(shù)表的內(nèi)存地址,是 TargetClassDescriptor 中的最后一個(gè)成員變量,添加方法的形式是追加到數(shù)組的末尾。所以這個(gè)虛函數(shù)表是按順序連續(xù)存儲(chǔ)類的方法的指針。

extension中方法調(diào)用

  • 在原有Test類基礎(chǔ)上添加extension,并添加test2方法
extension Test {
    func test2() {
        
    }
}
  • 通過匯編查看
image
  • 我們發(fā)現(xiàn)它并沒有獲取metedata,進(jìn)行偏移的方式來(lái)獲取函數(shù)地址,而是通過地址直接進(jìn)行調(diào)用,也就是采用靜態(tài)派發(fā)方式。
  • 這里方法為什么沒有添加到函數(shù)表中呢?
    • 一方面是類是可以繼承的,如果給父類添加extension方法,繼承該類的所有子類都可以調(diào)用這些方法。并且每個(gè)子類都有自己的函數(shù)表,所以這個(gè)時(shí)候方法存儲(chǔ)就成為問題。
  • 所以為了解決這個(gè)問題,直接把 extension 獨(dú)立于虛函數(shù)表之外,采用靜態(tài)調(diào)用的方式。在程序進(jìn)行編譯的時(shí)候,函數(shù)的地址就已經(jīng)知道了。

修飾函數(shù)的關(guān)鍵字

  • final: 添加了final關(guān)鍵字的函數(shù)無(wú)法被寫, 使用靜態(tài)派發(fā), 不會(huì)在vtable中出現(xiàn), 且對(duì)objc運(yùn)行時(shí)不可見。 如果在實(shí)際開發(fā)過程中,屬性、方法、類不需要被重載的時(shí)候,可以添加final關(guān)鍵字。

  • dynamic: 函數(shù)均可添加dynamic關(guān)鍵字,為非objc類和值類型的函數(shù)賦予動(dòng)態(tài)性,但派發(fā)方式還是函數(shù)表派發(fā)。

  • @objc: 該關(guān)鍵字可以將swift函數(shù)暴露給Objc運(yùn)行時(shí), 依舊是函數(shù)表派發(fā)。

  • @objc + dynamic: 消息發(fā)送的方式。

總結(jié)

  • Swift中的方法調(diào)用分為靜態(tài)派發(fā)動(dòng)態(tài)派發(fā)兩種
  • 值類型中的方法就是靜態(tài)派發(fā)
  • 引用類型中的方法就是動(dòng)態(tài)派發(fā),其中函數(shù)的調(diào)度是通過V-Table函數(shù)表來(lái)進(jìn)行調(diào)度的
類型 調(diào)度方式 extension
值類型 靜態(tài)派發(fā) 靜態(tài)派發(fā)
函數(shù)表派發(fā) 靜態(tài)派發(fā)
NSObject子類 函數(shù)表派發(fā) 靜態(tài)派發(fā)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容