Runtime底層原理之isa解讀

Snip20190816_6.png

前一篇講解了一下Runtime的底層原理,objc_msgSend的消息發(fā)送流程;其實(shí)學(xué)習(xí)Runtime,首先要了解它底層的一些常用數(shù)據(jù)結(jié)構(gòu),比如isa指針;在arm64架構(gòu)之前,isa就是一個(gè)普通的指針,存儲(chǔ)著Class、Meta-Class對(duì)象的內(nèi)存地址;從arm64架構(gòu)開(kāi)始,對(duì)isa進(jìn)行了優(yōu)化,變成了一個(gè)共用體(union)結(jié)構(gòu),還使用位域來(lái)存儲(chǔ)更多的信息。

共用體:

共用體把幾種不同數(shù)據(jù)類型的變量存放在同一塊內(nèi)存里。共用體中的變量共享同一塊內(nèi)存。
union的主要特征有:

  • union中可以定義多個(gè)成員,union的大小由最大的成員的大小決定;
  • union成員共享同一塊大小的內(nèi)存,一次只能使用其中的一個(gè)成員;
  • 對(duì)union某一個(gè)成員賦值,會(huì)覆蓋其他成員的值(但前提是成員所占字節(jié)數(shù)相同,當(dāng)成員所占字節(jié)數(shù)不同時(shí)只會(huì)覆蓋相應(yīng)字節(jié)上的值,比如對(duì)char成員賦值就不會(huì)把整個(gè)int成員覆蓋掉,因?yàn)閏har只占一個(gè)字節(jié),而int占四個(gè)字節(jié));
  • union量的存放順序是所有成員都從低地址開(kāi)始存放的。

isa的結(jié)構(gòu)如下:

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

#if SUPPORT_PACKED_ISA

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL        //這個(gè)很重要后面會(huì)講到??
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };

# elif __x86_64__
...下面代碼省略不做重點(diǎn)介紹了??

};

isa參數(shù)詳解

  • nonpointer:
    0,代表普通的指針,存儲(chǔ)著Class、Meta-Class對(duì)象的內(nèi)存地址
    1,代表優(yōu)化過(guò),使用位域存儲(chǔ)更多的信息
  • has_assoc:是否有設(shè)置過(guò)關(guān)聯(lián)對(duì)象,如果沒(méi)有,釋放時(shí)會(huì)更快
  • has_cxx_dtor:是否有C++的析構(gòu)函數(shù)(.cxx_destruct),如果沒(méi)有,釋放時(shí)會(huì)更快
  • shiftcls:存儲(chǔ)著Class、Meta-Class對(duì)象的內(nèi)存地址信息
  • magic:用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化
  • weakly_referenced:是否有被弱引用指向過(guò),如果沒(méi)有,釋放時(shí)會(huì)更快
  • deallocating:對(duì)象是否正在釋放
  • extra_rc:表示該對(duì)象的引用計(jì)數(shù)值,實(shí)際上是引用計(jì)數(shù)值減 1,例如,如果對(duì)象的引用計(jì)數(shù)為 10,那么 extra_rc 為 9。如果引用計(jì)數(shù)大于 10,則需要使用到下面的 has_sidetable_rc。
  • has_sidetable_rc:當(dāng)對(duì)象引用計(jì)數(shù)大于 10 時(shí),則has_sidetable_rc 的值為 1,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫 SideTable 的類的屬性中,這是一個(gè)散列表。原文鏈接

扯了這么多到底什么是isa?官方介紹是這樣的:
Every object is connected to the run-time system through itsisa instance variable, inherited from the NSObject class.isa identifies the object's class; it points to a structurethat's compiled from the class definition. Through isa, anobject can find whatever information it needs at run timesuch asits place in the inheritance hierarchy, the size and structure ofits instance variables, and the location of the methodimplementations it can perform in response to messages.
(每個(gè)對(duì)象都通過(guò)從NSObject類繼承的實(shí)例變量itsisa連接到運(yùn)行時(shí)系統(tǒng)。isa標(biāo)識(shí)對(duì)象的類;它指向一個(gè)從類定義編譯而來(lái)的結(jié)構(gòu)。通過(guò)isa,一個(gè)對(duì)象可以在運(yùn)行時(shí)找到它需要的任何信息,比如它在繼承層次結(jié)構(gòu)中的位置、它的實(shí)例變量的大小和結(jié)構(gòu),以及它在響應(yīng)消息時(shí)可以執(zhí)行的methodimplementations的位置。)

一個(gè)對(duì)象(Object)的isa指向了這個(gè)對(duì)象的類(Class),而這個(gè)對(duì)象的類(Class)的isa指向了metaclass。這樣我們就可以找到靜態(tài)方法和變量了。Objective-C的運(yùn)行時(shí)是動(dòng)態(tài)的,它能讓你在運(yùn)行時(shí)為類添加方法或者去除方法以及使用反射(反射什么鬼?傳送門)。

講isa就一定會(huì)提到metaclass,這里先提一下什么是metaclass?

  • meta-class是一個(gè)類對(duì)象的類
  • 當(dāng)我們向一個(gè)對(duì)象發(fā)送消息時(shí),runtime會(huì)在這個(gè)對(duì)象所屬的這個(gè)類的方法列表中查找方法;而向一個(gè)類發(fā)送消息時(shí),會(huì)在這個(gè)類的meta-class的方法列表中查找。
  • meta-class 是必須的,因?yàn)樗鼮橐粋€(gè) Class 存儲(chǔ)類方法。每個(gè)Class都必須有一個(gè)唯一的 meta-class,因?yàn)槊總€(gè)Class的類方法基本不可能完全相同。

問(wèn)題提出:iOS中有靜態(tài)方法與動(dòng)態(tài)方法,那么兩種方法的異同是什么?(問(wèn)題來(lái)源:腳本之家-lqh )

因?yàn)槊總€(gè)對(duì)象都由相應(yīng)的數(shù)據(jù)結(jié)構(gòu)與方法相構(gòu)成,一個(gè)程序可能有多個(gè)屬于同一個(gè)類的對(duì)象,而每個(gè)對(duì)象的數(shù)據(jù)結(jié)構(gòu)應(yīng)該是不一的,但方法是相同的,若為每個(gè)對(duì)象開(kāi)辟內(nèi)存空間來(lái)存儲(chǔ)方法,必然是對(duì)內(nèi)存空間極大的浪費(fèi)。因此apple是通過(guò)類對(duì)象與元類來(lái)解決這個(gè)問(wèn)題的。

對(duì)象的底層實(shí)際上就是結(jié)構(gòu)體,其有兩個(gè)重要的指針,一個(gè)是isa指針,一個(gè)是super指針。
isa指針:負(fù)責(zé)指向類對(duì)象,用來(lái)表明自己是什么類類型,并能調(diào)用類對(duì)象中的動(dòng)態(tài)方法。
super指針:表示對(duì)象的繼承關(guān)系,指向父類,從而能調(diào)用父類的相應(yīng)方法。
類對(duì)象:類對(duì)象是由元類生成的對(duì)象,負(fù)責(zé)存儲(chǔ)動(dòng)態(tài)方法,動(dòng)態(tài)方法在編譯器是不確定的,因此編譯器也無(wú)法檢測(cè)與動(dòng)態(tài)方法相關(guān)的錯(cuò)誤,動(dòng)態(tài)方法的調(diào)用是在運(yùn)行期中通過(guò)消息機(jī)制來(lái)執(zhí)行的,因此也只有運(yùn)行期才會(huì)報(bào)錯(cuò)。


image.png

結(jié)論:

兩者的差異包括:

  1. 方法列表是區(qū)分開(kāi)的,分別存儲(chǔ)在類對(duì)象與元類中。
  2. 動(dòng)態(tài)方法是在運(yùn)行期中調(diào)用,編譯器無(wú)法檢測(cè)錯(cuò)誤,靜態(tài)方法是在編譯器就確定,編譯器能檢測(cè)錯(cuò)誤。
  3. 動(dòng)態(tài)方法由對(duì)象調(diào)用,靜態(tài)方法由類調(diào)用,因?yàn)檎{(diào)用方法是通過(guò)isa和super指針實(shí)現(xiàn)的。因此對(duì)象只能調(diào)用類對(duì)象的方法,而類對(duì)像能調(diào)用元類的方法。
isa.png

這張圖描述如下:

  • 類的實(shí)例對(duì)象的 isa 指向它的類;類的 isa 指向該類的 metaclass;
  • 類的 super_class 指向其父類,如果該類為根類則值為 NULL;
  • metaclass 的 isa 指向根 metaclass,如果該 metaclass 是根 metaclass則指向自身;
  • metaclass 的 super_class 指向父 metaclass,如果該 metaclass 是根 metaclass則指向該 metaclass 對(duì)應(yīng)的類;
  • Object-C 為每個(gè)類的定義生成兩個(gè) objc_class ,一個(gè)普通的 class,另一個(gè)即metaclass。我們可以在運(yùn)行期創(chuàng)建這兩個(gè) objc_class 數(shù)據(jù)結(jié)構(gòu),然后使用 objc_addClass將 class注冊(cè)到運(yùn)行時(shí)系統(tǒng)中,以此實(shí)現(xiàn)動(dòng)態(tài)地創(chuàng)建一個(gè)新的類。

為什么這里說(shuō)生成兩個(gè) objc_class ,從前面metaclass就可以了解一二了。講到這里,大家可能很疑惑isa到底是怎么指向類的,解釋如下:isa里面存儲(chǔ)各種信息,是一個(gè)共用體,其中shiftcls 33位才是用來(lái)存放地址,通過(guò)&ISA_MASK就可以將33位的地址值取出來(lái),看圖分析理解更透徹:

isa圖解,來(lái)源于MJ

因?yàn)橄旅嬉玫絚lass 講解,我們先來(lái)看看 objc_class 的定義,然后來(lái)個(gè)實(shí)例和圖解進(jìn)行分析:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

稍微解釋一下各個(gè)參數(shù)的意思:

  • isa:是一個(gè)Class 類型的指針. 每個(gè)實(shí)例對(duì)象有個(gè)isa的指針,他指向?qū)ο蟮念?,而Class里也有個(gè)isa的指針, 指向meteClass(元類)。元類保存了類方法的列表。當(dāng)類方法被調(diào)用時(shí),先會(huì)從本身查找類方法的實(shí)現(xiàn),如果沒(méi)有,元類會(huì)向他父類查找該方法。同時(shí)注意的是:元類(meteClass)也是類,它也是對(duì)象。元類也有isa指針,它的isa指針最終指向的是一個(gè)根元類(root meteClass).根元類的isa指針指向本身,這樣形成了一個(gè)封閉的內(nèi)循環(huán)。
  • super_class:父類,如果該類已經(jīng)是最頂層的根類,那么它為NULL。
  • version:類的版本信息,默認(rèn)為0
  • info:供運(yùn)行期使用的一些位標(biāo)識(shí)。
  • instance_size:該類的實(shí)例變量大小
  • ivars:成員變量的數(shù)組

下面來(lái)個(gè)例子:

  1. 新建一個(gè)類Parent,繼承于NSObject, 里面有成員方法-(void)selectorP,類方法+(void)ClassSelectorP。
  2. 新建一個(gè)類Child,繼承于Parent,里面有成員方法-(void)selectorC, 類方法+(void)ClassSelectorC.

現(xiàn)在我們新建一個(gè)實(shí)例Child* child = [Chlid new];

  1. 當(dāng)我們調(diào)用[child class] 的時(shí)候,child就會(huì)通過(guò)isa指針去找到Child。
  2. 當(dāng)我們調(diào)用[child superclass]的時(shí)候,child 通過(guò)isa找到Child,再通過(guò)Child的isa,找到Parent。
    對(duì)象方法
  3. 接著,調(diào)用[child SelectorC],child通過(guò)isa找到Child,在Child的方法列表里面找到SelectorC;
  4. 再試著調(diào)用[child SelectorP],child通過(guò)isa找到Child,發(fā)現(xiàn)Child里面并沒(méi)有這個(gè)方法,再通過(guò)Child的isa,找到Parent,在Parent里面的方法列表找到了SelectorP;
    類方法
  5. 再是類方法[Child ClassSelectorC],Child(請(qǐng)注意是類調(diào)用)通過(guò)isa找到Child的metaclass,在metaclass的方法列表里面找到了ClassSelectorC;
  6. 再試著調(diào)用[Child ClassSelectorP],Child通過(guò)isa找到Child的metaclass,發(fā)現(xiàn)metaclass里面并沒(méi)有這個(gè)方法,通過(guò)metaclass里面的isa找到Parent的metaclass,在里面的方法列表找到了ClassSelectorP;
  7. 所有的元類最終繼承一個(gè)根元類,根元類isa指針指向本身,形成一個(gè)封閉的內(nèi)循環(huán)
    圖解如下:


    靈魂畫(huà)手??

isa基本就講完了,下面來(lái)個(gè)小李子再次加深理解:
NSArray *array = [[NSArray alloc] init];流程剖析

  1. [NSArray alloc]先被執(zhí)行。先去NSArray中查找+alloc方法(類方法),因?yàn)镹SArray沒(méi)有+alloc方法,于是去父類NSObject去查找。
  2. 檢測(cè)NSObject是否響應(yīng)+alloc方法,發(fā)現(xiàn)響應(yīng),于是檢測(cè)NSArray類,并根據(jù)其所需的內(nèi)存空間大小開(kāi)始分配內(nèi)存空間,然后把isa指針指向NSArray類。同時(shí),+alloc也被加進(jìn)cache列表里面。
  3. 接著,執(zhí)行-init方法,如果NSArray響應(yīng)該方法,則直接將其加入cache;如果不響應(yīng),則去父類查找。
  4. 在后期的操作中,如果再以[[NSArray alloc] init]這種方式來(lái)創(chuàng)建數(shù)組,則會(huì)直接從cache中取出相應(yīng)的方法,直接調(diào)用。

總結(jié):

  • isa是一個(gè)共用體;isa標(biāo)識(shí)對(duì)象的類;它指向一個(gè)從類定義編譯而來(lái)的結(jié)構(gòu)。通過(guò)isa,一個(gè)對(duì)象可以在運(yùn)行時(shí)找到它需要的任何信息,比如它在繼承層次結(jié)構(gòu)中的位置、它的實(shí)例變量的大小和結(jié)構(gòu),以及它在響應(yīng)消息時(shí)可以執(zhí)行的imp的位置;
  • 也可以說(shuō)isa是一個(gè)Class 類型的指針,每個(gè)實(shí)例對(duì)象有個(gè)isa的指針,他指向?qū)ο蟮念惤Y(jié)構(gòu)。
  • Objective-C 2.0中的描述是:新的對(duì)象被創(chuàng)建,其內(nèi)存同時(shí)被分配,實(shí)例變量也同時(shí)被初始化;對(duì)象的第一個(gè)實(shí)例變量是一個(gè)指向 該對(duì)象的類結(jié)構(gòu)的指針,叫做 isa。通過(guò)該指針,對(duì)象可以訪問(wèn)它對(duì)應(yīng)的類以及相應(yīng)的父類。

文章參考:
http://www.itdecent.cn/p/cf93dc9d2262
http://www.itdecent.cn/p/83b9f172c43c

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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