iOS 底層探索篇 —— isa的初始化&指向分析

前言c++

isa的引出

1. 從內存段分析

iOS 底層探索篇 —— 內存字節對齊分析這篇文章中,咱們經過lldb調試的時候,第一個內存段咱們並非直接打印po出來的,而是po 0x00000001029570d0 & 0x0000000ffffffff8這樣的操做,來打印出來的對象。bash

0x00000001029570d0這片內存段其實就是isa0x0000000ffffffff8這個值就是ISA_MASK掩碼的值。架構

2. 從對象的本質分析

iOS 底層探索篇 —— 對象的本質這篇文章中,咱們知道了對象的本質就是結構體,經過繼承關係找到父類在底層就是objc_objcet的成員isaide

isa的初始化

1. 初始化流程

iOS 底層探索篇 —— alloc、init、new的探索這篇文章中,咱們知道會調用一個initIsa()的函數。函數

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());

        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 = newisa;
    }
}
複製代碼
  • nonpointer,就直接賦值cls
  • nonpointer就會作一些初始化的賦值。

2. isa數據類型分析

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
};
複製代碼
  • union聯合體,一種數據類型,所佔用8個字節。
  • 聯合體的特性:內存共用,或者說帶有互斥特性,意思就是賦值了cls,就不對其餘成員賦值了。

分析ISA_BITFIELDpost

# 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)

# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# 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
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)

# else
# error unknown architecture for packed isa
# endif
複製代碼

ISA_BITFIELD裏面的字段field是相同的,可是在不一樣的架構平臺下,每一個field對應的位域會有不一樣。下面介紹每一個field的意思優化

  • nonpointer表示是否對isa指針開啓優化(咱們如今的都是開啓了優化),值爲0:純isa指針; 值爲1:不止是類對象地址,isa中還包含了類信息,對象的引用計數等;
  • has_assoc是否有關聯對象,值0無,值1有。
  • has_cxx_dtor是否有c++或者objc析構函數,若是有析構函數,先走析構邏輯,沒有就更快的釋放對象。
  • shiftcls存儲類指針的值,開啓指針優化的時候,在arm64架構中有33位存儲類指針。
  • magic用來調試器判斷當前對象是真的對象仍是沒有初始化的空間。
  • weakly_referenced標誌對象是否指向或者曾經指向一個ARC的弱變量,沒有弱引用的對象能夠更快的釋放。
  • deallocating標誌對象是否正在釋放。
  • has_sidetable_rc當對象的引用計數大於10的時候,會借用該變量存儲進位。
  • extra_rc表示該對象的引用計數的值,其實是引用計數的值減去1。 例如:該對象的引用計數爲10,則該變量的值爲9,若超過10,則須要用到has_sidetable_rc

isa的指向分析

1. 對象與類的關聯

XDPerson *person = [XDPerson alloc];
複製代碼

1.1 經過控制檯調試ui

簡單介紹一下lldb命令this

  • x/4gx objc打印objc的4段內存信息。擴展:x/6gx就是打印6段內存信息。
  • p/tp/t 打印二進制信息;p/o打印八進制信息;p/x打印十六進制信息;

ISA_BITFIELD裏面的shiftCls咱們瞭解到是存儲類的指針的。spa

(lldb) x/4gx person //打印對象person的內存信息
0x1018482f0: 0x001d800100001129 0x0000000000000000
0x101848300: 0x00000001018483d0 0x0000000101848610

(lldb) p/t XDPerson.class //打印XDPerson的二進制值
(Class) $1 = 0b0000000000000000000000000000000100000000000000000001000100101000 XDPerson

(lldb) p/t 0x001d800100001129 //person的isa的二進制值
(long) $2 = 0b0000000000011101100000000000000100000000000000000001000100101001

(lldb) p/t $2>>3<<3 //shiftcl 前面有三位 咱們須要右移3位 而後左移還原位置
(long) $3 = 0b0000000000011101100000000000000100000000000000000001000100101000

(lldb) p/t $3<<17>>17 //由於是模擬器_x86_64 shiftcl後面還有17位 故先左移17位找到 而後右移17位還原位置
(long) $4 = 0b0000000000000000000000000000000100000000000000000001000100101000
複製代碼
  • 經過上面的調試咱們會發現$1$4的值是相同的,驗證了對象personisa是和類XDPerson關聯上了。

1.2 經過objc_getClass()調試 這裏提供給一個objc的源碼

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
複製代碼
inline Class 
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()函數。

inline Class 
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.

  • 能夠直接知道isa.bits & ISA_MASK這麼一個&運算來獲取的。
  • 同時咱們也能夠了解objc->getIsa()返回的就是Class

下面咱們經過lldb調試直接定位到cls

(lldb) x/4gx person     //打印對象的內存信息
0x101d047e0: 0x001d800100001129 0x0000000000000000
0x101d047f0: 0x00007fff9b5ff170 0x00000000c3000002

(lldb) p/x XDPerson.class  //打印類的地址
(Class) $1 = 0x0000000100001128 XDPerson

(lldb) p/x 0x001d800100001129 & 0x0000000ffffffff8 //經過對象的isa & ISA_MASK
(long) $2 = 0x0000000100001128
複製代碼

直接能夠觀察到$1$2的值是相同的,也驗證了對象的isa指向了類。

2. isa的在類和元類之間的遊走

2.1 類在底層的本質

這裏先提供給一個源碼,用來描述類的本質在底層是objc_class,同時也繼承自objc_object,說明objc_class第一個成員也是isa

typedef struct objc_class *Class;
struct objc_class : objc_object{};
複製代碼

2.1 isa的繼續走位 咱們已經瞭解到了對象的isa指向了類,從而來綁定對象與類。那麼類的isa又會怎麼走呢。

咱們繼續經過lldb來調試。

XDPerson *person = [XDPerson alloc];
複製代碼
  • isa從對象指向類
(lldb) x/4gx person
0x10185eac0: 0x001d8001000011a9 0x0000000000000000
0x10185ead0: 0x000000010185eba0 0x000000010185ede0
(lldb) p/x 0x001d8001000011a9 & 0x0000000ffffffff8
(long) $1 = 0x00000001000011a8
(lldb) po $1
XDPerson
複製代碼

咱們打印person的內存信息 經過對象的isa & ISA_MASK 獲取到了類的信息。即isa從類的實例對象person指向了類XDPerson

  • isa從類指向元類
(lldb) x/4gx $1
0x1000011a8: 0x001d800100001181 0x00000001000011f8
0x1000011b8: 0x00000001003a1e50 0x0000000000000000
(lldb) p/x 0x001d800100001181 & 0x0000000ffffffff8
(long) $2 = 0x0000000100001180
(lldb) po $2
XDPerson
複製代碼

咱們打印類XDPerson的內存信息,經過類的isa&ISA_MASK獲取到了另一個類的信息。即isa從類XDPerson又指向了類XDPerson(其實這個類是咱們XDPersonmetaClass元類,它與第一步的XDPerson的內存地址並不一樣)。咱們編譯器會把一個類做爲它的元類的實例化對象來處理,就像一個對象從類實例化出來的模式。

  • isa從元類指向根元類
(lldb) x/4gx $2
0x100001180: 0x001d800100aff0f1 0x00000001000011d0
0x100001190: 0x0000000101e142b0 0x0000000100000007
(lldb) p/x 0x001d800100aff0f1 & 0x0000000ffffffff8
(long) $3 = 0x0000000100aff0f0
(lldb) po $3
NSObject
複製代碼

咱們打印元類XDPerson的內存信息 經過元類的isa & ISA_MASK 獲取到了另一個元類類NSObject的信息。即isa從元類XDPerson又指向了元類NSObject。這裏咱們能夠來打印NSObjcr.class來觀察,元類NSObject的內存地址NSObjcr.class的內存地址並不一樣。

  • isa從根元類指向根根元類
(lldb) x/4gx $3
0x100aff0f0: 0x001d800100aff0f1 0x0000000100aff140
0x100aff100: 0x0000000101e146e0 0x0000000300000007
(lldb) p/x 0x001d800100aff0f1 & 0x0000000ffffffff8
(long) $4 = 0x0000000100aff0f0
(lldb) po $4
NSObject
複製代碼

咱們打印元類NSObject的內存信息,經過元類的isa & ISA_MASK獲取到了根元類NSObject的信息。即isa 從元類NSObject指向了根元類NSObject。其實作到這裏咱們就能夠看到了 第三步的NSObject與第四步的NSObject是同一片內存地址。

經過上面的lldb調試咱們基本上了解到了isa的指向走位分析,下面就把蘋果官方提供的isa的走位圖拿出來作一下解釋

  • 虛線表明了isa的走位。實例對象->類->元類->根元類->根根元類(根元類自己)。
  • 實線表明了繼承關係。這裏值得注意的就是根元類的父類是NSObject

繼承關係

類的結構體的補充

struct objc_class {
    //Class isa
    Class superclass;
    ...省略部分...
}
複製代碼

說明類的第二段內存是指向父類。

類的繼承關係

  1. 定義繼承關係

XDTeacher -> XDPerson -> NSObject

  1. 驗證繼承關係
  • XDTeacher的父類驗證
(lldb) x/4gx XDTeacher.class
0x1000011f8: 0x001d8001000011d1 0x00000001000011a8
0x100001208: 0x00000001003a1e50 0x0000000000000000
(lldb) po 0x00000001000011a8
XDPerson
複製代碼
  • XDPerson的父類驗證
(lldb) x/4gx 0x00000001000011a8
0x1000011a8: 0x001d800100001181 0x0000000100aff140
0x1000011b8: 0x00000001003a1e50 0x0000000000000000
(lldb) po 0x0000000100aff140
NSObject
複製代碼
  • NSObject的父類驗證
(lldb) x/4gx 0x0000000100aff140
0x100aff140: 0x001d800100aff0f1 0x0000000000000000
0x100aff150: 0x000000010105b980 0x0000000100000003
(lldb) po 0x0000000000000000
<nil>
複製代碼

驗證了類的繼承關係是XDTeacher -> XDPerson -> NSObject -> nil

元類的繼承關係

  1. 類的繼承鏈上的內存地址
(lldb) x/4xg XDTeacher.class
0x1000011f8: 0x001d8001000011d1 0x00000001000011a8
0x100001208: 0x00000001003a1e50 0x0000000000000000
(lldb) po 0x00000001000011a8
XDPerson

(lldb) x/4xg 0x00000001000011a8
0x1000011a8: 0x001d800100001181 0x0000000100aff140
0x1000011b8: 0x00000001003a1e50 0x0000000000000000
(lldb) po 0x0000000100aff140
NSObject
複製代碼

咱們作好準備條件把類的父類的內存地址所有先獲取。

  1. 元類的內存地址
  • XDTeacher的元類內存地址和元類的父類的內存地址
(lldb) p/x 0x001d8001000011d1 & 0x0000000ffffffff8
(long) $3 = 0x00000001000011d0
(lldb) x/4xg 0x00000001000011d0
0x1000011d0: 0x001d800100aff0f1 0x0000000100001180
0x1000011e0: 0x00000001018002a0 0x0000000300000003
複製代碼

咱們獲取到了XDTeacher元類的父類的內存地址0x0000000100001180

  • XDPerson的元類內存地址
(lldb) x/4xg 0x00000001000011a8
0x1000011a8: 0x001d800100001181 0x0000000100aff140
0x1000011b8: 0x00000001003a1e50 0x0000000000000000
(lldb) p/x 0x001d800100001181 & 0x0000000ffffffff8
(long) $4 = 0x0000000100001180
複製代碼

XDPerson的元類的內存地址是0x0000000100001180,同XDTeacher元類的父類的內存地址時相同的。

  • 這一步就能夠驗證了XDTeacher元類繼承自XDPerson的元類
  • 所有驗證
//查看XDPerson的元類的內存信息
(lldb) x/4xg 0x0000000100001180
0x100001180: 0x001d800100aff0f1 0x0000000100aff0f0 -->XDPerson的元類的父類內存地址
0x100001190: 0x0000000101d09e50 0x0000000300000003

//查看NSObjec的內存信息
(lldb) x/4xg 0x0000000100aff140
0x100aff140: 0x001d800100aff0f1 0x0000000000000000
0x100aff150: 0x000000010183a780 0x0000000100000003
//查看NSObjec的元類內存地址
(lldb) p/x 0x001d800100aff0f1 & 0x0000000ffffffff8
(long) $5 = 0x0000000100aff0f0 -->驗證XDPerson的元類->NSObjec的元類

//查看根元類的內存信息
(lldb) x/4xg 0x0000000100aff0f0
0x100aff0f0: 0x001d800100aff0f1 0x0000000100aff140
0x100aff100: 0x0000000101d094c0 0x0000000300000007
//查看根元類的父類
(lldb) x/4xg 0x0000000100aff140
0x100aff140: 0x001d800100aff0f1 0x0000000000000000
0x100aff150: 0x000000010183a780 0x0000000100000003
//這裏我麼能夠提早獲取一下打印NSObject.class的內存信息 
//方便區分根元類NSObject 和 類NSObject 我這裏沒有打印
//po NSObject.class
//獲得根元類的父類NSObject
(lldb) p/x 0x001d800100aff0f1 & 0x0000000ffffffff8
(long) $7 = 0x0000000100aff0f0
(lldb) po $7
NSObject
(lldb) po 0x0000000000000000
<nil>
複製代碼

經過咱們一步步的調試咱們的元類的繼承關係就出來了。

  • XDTeacher元類->XDPerson元類->NSObject元類->NSObject->nil。

經過這一步的流程,也驗證了isa走位圖,筆者能力有限,探索不足之處能夠在評論區指正。

相關文章
相關標籤/搜索