OC底層研究3--isa的初始化和指向分析

本頁所使用的objc runtime 756.2,來自GITHUBc++

開始繼續學習研究OC源碼,此次研究的是isa的初始化和指向分析。git

1. 概念

什麼是isa

看看蘋果文檔的介紹:github

isaobjective-c

A Pointer to the class definition of which this object is an instance.bash

isa : 一個指向該對象的類的指針。架構

打開Xcode,找到objc.h,咱們能夠看看到以下代碼ide

#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
/// 一個展現OC類的未知的類型
typedef struct objc_class *Class;

/// Represents an instance of a class.
/// 展現一個類的實例
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
/// 一個指向類的實例的指針
typedef struct objc_object *id;
#endif
複製代碼

能夠看出,Class 是一個objc_class 類型的結構體。函數

而id類型,則是objc_object 類型的結構體.學習

2. isa 的初始化

在此以前,先回顧一下對象初始化的流程圖優化

在這裏,初始化實例的isa,其中 cls 爲初始化的類對象,hasCxxDtor 即爲是否含有C++的析構器。

咱們進入 initIsa(cls, true, hasCxxDtor) 這個函數,看看內部實現了什麼

2.1 isa非nonpointer

if (!nonpointer) {
    isa.cls = cls;
} 
複製代碼
  • nonpointer概念:

    表示是否對isa 指針開啓指針優化

0: 純isa指針

1: 不止是類對象的地址,還包含類信息、對象的引用計數等。

此時,若是爲純isa指針,將當前類 cls 賦值給 isa 的綁定屬性 cls

爲何有這個綁定屬性,而isa到底是什麼看結構呢?

點擊isa.cls = cls;中的cls查看它的結構,以下:

union isa_t {
    isa_t() { }					// isa 初始化方法
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
         ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
複製代碼

能夠看見isa 是一個 union,聯合體,裏面包含了

  • isa_t 初始化方法
  • isa_t(uintptr_t value) 工廠方法
  • Class cls 綁定屬性
  • 結構體ISA_BITFIELD位域
  1. ISA_BITFIELD概念

    咱們點開類型爲structISA_BITFIELD,結構以下:

    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    #   define ISA_MAGIC_MASK  0x000003f000000001ULL
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #   define ISA_BITFIELD                                                      \
          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)
    複製代碼
  2. NONPOINTER_ISA效果圖(手繪中,待補全......)

  3. 還原isa_t 的結構

    咱們這時發現,isa的總體結構能夠替換爲以下的樣子:

    union isa_t {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
    #if defined(ISA_BITFIELD)
        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;
            //         ISA_BITFIELD;  // defined in isa.h
        };
    #endif
    };
    複製代碼
  • nonpointer: 表示是否對 isa 指針開啓指針優化 0:純isa指針,1:不止是類對象地址**,isa** 中包含了類信息、對象的引用計數等(佔1位)

  • has_assoc: 關聯對象標誌位,0沒有,1存在(佔1位)

  • has_cxx_dtor: 該對象是否有 C++ 或者 Objc 的析構器**,若是有析構函數,則須要作析構邏輯,** 若是沒有**,**則能夠更快的釋放對象(佔1位)

  • **shiftcls:**存儲類指針的值。開啓指針優化的狀況下,在 arm64 架構中有 33 位用來存儲類指針。(佔33位)

  • magic:用於調試器判斷當前對象是真的對象仍是沒有初始化的空間 weakly_referenced:志對象是否被指向或者曾經指向一個 ARC 的弱變量,沒有弱引用的對象能夠更快釋放。(佔6位)

  • deallocating:標誌對象是否正在釋放內存(佔1位)

  • has_sidetable_rc:當對象引用計數大於 10 時,則須要借用該變量存儲進位(佔1位)

  • extra_rc:當表示該對象的引用計數值,其實是引用計數值減 1, 例如,若是對象的引用計數爲 10,那麼 extra_rc9。若是引用計數大於 10, 則須要使用到下面的 has_sidetable_rc。(佔1位)

2.2 isanonpointer類型

isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        assert(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif

複製代碼
  • 生成新的isa:

    isa_t newisa(0);

  • c++ 析構器:

    newisa.has_cxx_dtor = hasCxxDtor;` 表示當前對象是否有C++的析構函數(destructor),若是沒有,釋放時會快速的釋放內存。

  • 位域賦值

    newisa.shiftcls = (uintptr_t)cls >> 3;對存儲指針的值進行右移動3位賦值。

  • 返回isa

    isa = newisa;

3. isa 的指向

3.1 指向圖:

關於isa的指向以及子類父類的關係,蘋果官方給出了一張圖以下所示:

3.2 代碼分析

3.2.1 對象的isa

咱們執行一項代碼以下,並對該行打斷點:

Person *object = [Person alloc];
複製代碼

咱們知道:對象裏的 isa ——指向——> 類

3.2.2類的isa

如今咱們想知道類的內存空間結構,在控制檯執行以下指令x/4gx Person.class,結果以下:

(lldb) x/4gx Person.class
0x100001130: 0x001d800100001109 0x0000000100b39140
0x100001140: 0x0000000101a46ed0 0x0000000200000007
複製代碼

因爲isa是類對象的第一個屬性,咱們知道0x001d800100001109是改對象的isa,咱們看看他指向哪裏呢,使用p/x 指令試試:

(lldb) p/x 0x001d800100001109
(long) $16 = 0x001d800100001109
複製代碼

糟糕,查看不到結果?怎麼回事?類的isa 格式須要強轉,能夠退一步,打印類的地址試試:

po 0x100001130
Person
複製代碼

原來如此,在內存空間裏,名爲Person的類的第一個位置,指向Person類,豈不是循環指向了?

非也非也,這裏指向的類,咱們把它稱爲元類(meta-class)

類的isa ——指向——> 元類

3.2.3 元類的isa

咱們如今得到元類的具體地址,找到isaMASK(掩碼),值爲0x00007ffffffffff8

輸入如下指令:

(lldb) p/x 0x001d800100001109 & 0x00007ffffffffff8
(long) $17 = 0x0000000100001108
(lldb) po 0x0000000100001108
Person
複製代碼

獲得元類地址爲:0x0000000100001108,16進制打印一下:

(lldb) x/4gx 0x0000000100001108
0x100001108: 0x001d800100b390f1 0x0000000100b390f0
0x100001118: 0x0000000100f5a480 0x0000000400000007
複製代碼

能夠看到元類結構裏,isa指針爲 0x001d800100b390f1,繼續獲取它的指向,咱們經過與掩碼來計算:

(lldb) p/x 0x001d800100b390f1 & 0x00007ffffffffff8
(long) $21 = 0x0000000100b390f0
複製代碼

好嘞,拿到內存指針地址爲0x0000000100b390f0, 打印一下:

po 0x0000000100b390f0
NSObject
複製代碼

至此,咱們能夠看到元類的isa指向它的上一級元類,也就是跟元類(root meta-class),爲NSObject。

因此得出: 元類的isa ——指向——> 根元類

3.2.4 根元類的isa

咱們打印下根元類結構:

x/4gx 0x0000000100b390f0
0x100b390f0: 0x001d800100b390f1 0x0000000100b39140
0x100b39100: 0x0000000101a47020 0x0000000500000007
複製代碼

拿到它的isa,與掩碼繼續進行與運算

p/x 0x001d800100b390f1 & 0x00007ffffffffff8
(long) $27 = 0x0000000100b390f0
複製代碼

獲得的結果0x0000000100b390f0,與根元類0x0000000100b390f0,徹底吻合。

至此,咱們得出結論:根元類的isa ——指向——> 根類NSObject

什麼?你不信,這些都是猜想,證明一下?

好的,建立以下代碼

void TestNSObject(){
    // NSObject實例對象
    NSObject *object1 = [NSObject alloc];
    // NSObject類
    Class class = object_getClass(object1);
    // NSObject元類
    Class metaClass = object_getClass(class);
    // NSObject根元類
    Class rootMetaClass = object_getClass(metaClass);
    // NSObject根根元類
    Class rootRootMetaClass = object_getClass(rootMetaClass);
    NSLog(@"\n%p 實例對象\n%p 類\n%p 元類\n%p 根元類\n%p 根根元類",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
}
複製代碼

打印結果以下:

0x10066ddc0 實例對象
0x7fff9294a118 類
0x7fff9294a0f0 元類
0x7fff9294a0f0 根元類
0x7fff9294a0f0 根根元類
複製代碼

可見,除了NSObject 類是獨有的建立,其餘元類、根元類、根根元類,都是同樣的,由於都是NSObject,因此結果獲得了證實。

3.3 總結:

咱們再回到這幅圖,最紅是這樣的:

  • isa指向:

    • 對象中的isa——> 類
    • 類中的isa ——> 元類
    • 元類中的isa ----> 根元類
    • 根元類中的isa ----> 根元類
  • 類繼承關係:

    • 子類 ———superClass——— 父類
    • 父類 ———superClass——— 根元類
    • 根元類 ———superClass——— NSObject
    • NSObject ———superClass——— nil
相關文章
相關標籤/搜索