前言c++
isa
的引出iOS 底層探索篇 —— 內存字節對齊分析這篇文章中,咱們經過lldb
調試的時候,第一個內存段咱們並非直接打印po
出來的,而是po
0x00000001029570d0 & 0x0000000ffffffff8
這樣的操做,來打印出來的對象。bash
0x00000001029570d0
這片內存段其實就是isa
。0x0000000ffffffff8
這個值就是ISA_MASK
掩碼的值。架構
iOS 底層探索篇 —— 對象的本質這篇文章中,咱們知道了對象的本質就是結構體,經過繼承關係找到父類在底層就是objc_objcet
的成員isa
。ide
isa
的初始化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
就會作一些初始化的賦值。
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_BITFIELD
。post
# 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
的指向分析XDPerson *person = [XDPerson alloc];
複製代碼
1.1 經過控制檯調試ui
簡單介紹一下
lldb
命令this
x/4gx
objc
打印objc
的4段內存信息。擴展:x/6gx
就是打印6段內存信息。p/t
p/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
的值是相同的,驗證了對象person
的isa
是和類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
指向了類。
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
(其實這個類是咱們XDPerson
的metaClass
元類,它與第一步的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;
...省略部分...
}
複製代碼
說明類的第二段內存是指向父類。
XDTeacher
-> XDPerson
-> NSObject
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
。
(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
複製代碼
咱們作好準備條件把類的父類的內存地址所有先獲取。
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
走位圖,筆者能力有限,探索不足之處能夠在評論區指正。