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編代碼:

- 可以發(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() {
}
}
- 通過匯編查看

-
struct的extension的方法依然是直接調(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)試

-
在看匯編代碼前,可以簡(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è)步驟- 找到
metadata - 確定函數(shù)地址(
metadata+ 偏移量) - 執(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屬性,不管是Class,Struct還是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() {
}
}
- 通過匯編查看

- 我們發(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ā) |