iOS底層探究-isa的初始化&走位分析

本文可能篇幅比較長,主要爲了本身往後溫習知識所用。若是有幸被你發現這篇文章,而且引發了你的閱讀興趣,但願這篇文章能對你有所幫助。如發現任何有誤之處,肯請留言糾正,謝謝。️bash

一:isa是什麼?

iOS底層探究-淺談alloc,init,new 前面的文章中咱們提到過,基類NSObject有個默認的屬性isa:架構

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}
複製代碼

那麼isa是什麼呢?
蘋果有段官方的描述: 「A pointer to the class definition of which this object is an instance」。
isa實際上是指一個實例對象指向該對象的類的指針。沒錯,早期的時候isa的確是單純的一個指針,只不事後期蘋果進行了優化,不但保存了指針的地址,另外也存儲了一些類的信息。下面咱們看下蘋果isa的源代碼:ide

union isa_t {
    //兩個默認的構造函數
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

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

能夠看出isa實際上是個union聯合體,蘋果這塊用聯合體優化內存佔用,而且使用位域ISA_BITFIELD增長可讀性。聯合體和位域的結合使用,我會單獨去寫,請持續關注,咱們先看下 ISA_BITFIELD 位域的定義和使用。
定義:函數

# define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8
複製代碼

根據Union聯合體的特性 isa_t 總共佔用8個字節64位工具

其中:
nonpointer表明是不是純指針 0 表明純指針 1 表明不止是類對象地址,isa 中包含了類信息、對象的引用計數等
has_assoc 關聯對象標誌位
has_cxx_dtor 該對象是否有 C++ 或者 Objc 的析構器,若是有析構函數,則須要作析構邏輯, 若是沒有,則能夠更快的釋放對象
shiftcls 存儲類指針的值。開啓指針優化的狀況下,在 arm64 架構中有 33 位用來存儲類指針
magic 用於調試器判斷當前對象是真的對象仍是沒有初始化的空間
weakly_referenced 存儲對象是否被指向或者曾經指向一個 ARC 的弱變量, 沒有弱引用的對象能夠更快釋放。
deallocating 標誌對象是否正在釋放內存
has_sidetable_rc 當對象引用技術大於 10 時,則須要借用該變量存儲進位
extra_rc 當表示該對象的引用計數值,其實是引用計數值減 1, 例如,若是對象的引用計數爲 10,那麼 extra_rc 爲 9。若是引用計數大於 10, 則須要使用到下面的 has_sidetable_rcpost

二:isa指針走位

前面咱們已經看到了isa_t結構體保存了類的不少信息,其中就包括了類指針的值。下面咱們就研究下isa的指針走位。先看一張大神畫的圖:優化

isa流程圖

圖中實線箭頭表明類繼承的走向,而虛線箭頭是表明isa的走向ui

其實我第一次看這張圖的時候是懵逼的,亂七八糟的線根本看不出是表達什麼邏輯。後來隨着對底層實現的瞭解和探索,才慢慢了解了圖做者想表達的意思,下面我就和你們一塊兒來研究一下this

###1:靜態分析,咱們要研究isa,先看下系統是怎樣獲取類對象的: object_getClass()是系統獲取類對象的一個函數,內部是這樣實現的spa

Class object_getClass(id obj) {
    if (obj) return obj->getIsa();
    else return Nil;
}


objc_object::getIsa() 
{
    if (!isTaggedPointer()) return ISA();

    uintptr_t ptr = (uintptr_t)this;
    if (isExtTaggedPointer()) {
        uintptr_t slot = 
            (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        return objc_tag_ext_classes[slot];
    } else {
        uintptr_t slot = 
            (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
        return objc_tag_classes[slot];
    }
}
複製代碼

咱們知道objc都是!isTaggedPointer的,能夠直接定位到ISA()函數。

objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}
複製代碼

SUPPORT_INDEXED_ISA這個宏定義在iOS是0.

(Class)(isa.bits & ISA_MASK)這句話就是咱們的重點了,系統經過&運算結合mask來獲得isa類指針的值 最後(Class)作了步強轉返回Class

###2:動態調試,模擬靜態代碼的步驟,經過lldb工具印證咱們的推測 咱們經過objc最新的源碼,創建一個target方便咱們調試。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
      
        JSPerson *object = [JSPerson alloc];
        // 斷點位置
    }
    return 0;
}
複製代碼
  • isa從對象指向類

添加斷點,咱們經過lldb來調試

(lldb) x/2xg object // 打印類的信息
0x101a4c8b0: 0x001d800100001101 0x0000000000000000
複製代碼

其中0x001d800100001101就是對象中的isa咱們坐下運算

(lldb) p/x 0x001d800100001101 & 0x0000000ffffffff8
(long) $2 = 0x0000000100001100
複製代碼

而後對對象所屬的類進行打印

(lldb) p/x object.class
(Class) $3 = 0x0000000100001100 JSPerson
複製代碼

咱們看到了$2$3是相等的,也就印證了咱們的第一步,對象和類是靠isa創建綁定的

那麼類的isa會指向何處呢?咱們先看下底層對類結構體的定義:

struct objc_class : objc_object // 類在底層實際上是個繼承於objc_object的結構體,萬物皆對象在這裏獲得印證

// 咱們看下objc_class這個結構體
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;
複製代碼

說明objc_class第一個成員也是isa,那咱們繼續打印

  • isa從類指向元類
(lldb) x/2xg 0x0000000100001100
0x100001100: 0x001d8001000010d9 0x0000000100b00140

(lldb) p/x 0x001d8001000010d9 & 0x0000000ffffffff8
(long) $4 = 0x00000001000010d8

(lldb) po 0x00000001000010d8
JSPerson
複製代碼

咱們看到輸出的又是JSPerson,可是內存地址卻不相同,其實他是JSPerson的元類(metaClass),metaClass 是 Class 對象的類,一樣也是個對象。每一個類都必須有一個惟一的 metaClass.

  • isa從元類指向根元類
(lldb) x/2xg 0x00000001000010d8
0x1000010d8: 0x001d800100b000f1 0x0000000100b000f0

(lldb) p/x 0x001d800100b000f1 & 0x0000000ffffffff8
(long) $6 = 0x0000000100b000f0

(lldb) po 0x0000000100b000f0
NSObject
複製代碼

輸出了NSObject的元類也就是全部類的根元類

  • isa從根元類指向根根元類
(lldb)  x/2xg 0x0000000100b000f0
0x100b000f0: 0x001d800100b000f1 0x0000000100b00140

(lldb) p/x 0x001d800100b000f1 & 0x0000000ffffffff8
(long) $8 = 0x0000000100b000f0

(lldb) po 0x0000000100b000f0
NSObject
複製代碼

你會發現不管你打印多少次獲得的結果都是NSObject內存地址也不會變,造成了循環。

三:總結

咱們在把這張圖拿過來,如今感受是否是清晰了不少?

isa流程圖
isa走位總結:實例對象->類->元類->根元類->根根元類(根元類自己)
繼承關係 :Subclass->Superclass->Root class

下一篇文章 將對類的結構作跟細緻的研究,若是以爲喜歡的話還請加個關注,我會常常保持更新。

相關文章
相關標籤/搜索