作為iOS開發(fā)者取董,我們都知道繼承自NSObject
的子類都包含了一個(gè)isa
屬性竹揍,下圖是NSObject類的定義:
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
我們可以看到NSObject
類中有一個(gè)isa
屬性
isa詳解
在arm64之前蕴轨,isa就是普通的指針,只存儲(chǔ)類對(duì)象,元類對(duì)象的指針镣奋。但是arm64之后isa做了優(yōu)化后豫,采用了聯(lián)合體結(jié)構(gòu)悉尾,將64位的內(nèi)存空間用來(lái)存儲(chǔ)了非常多了標(biāo)記以及數(shù)據(jù)。
接下來(lái)我們通過objc源碼
來(lái)看看getIsa方法的返回:
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
值為0
所以挫酿,最終會(huì)走到下面這句代碼
return (Class)(isa.bits & ISA_MASK);
isa是一個(gè)isa_t
類型构眯,我們先來(lái)看看isa_t
的定義:
union isa_t {
isa_t() { }
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_t是一個(gè)聯(lián)合體類型,包含一個(gè)Class
類型的屬性cls
早龟,uintptr_t
類型的屬性bits
惫霸,以及一個(gè)struct
cls我們都明白是當(dāng)前類的類名,bits屬性代表什么意思呢拄衰?
如果不清楚聯(lián)合體對(duì)象的用法它褪,可以查看這篇文章聯(lián)合體
因?yàn)槁?lián)合體內(nèi)存共用的特性,struct
占用8個(gè)字節(jié)64位
內(nèi)存空間翘悉,我們看到uintptr_t
定義為:
#ifndef _UINTPTR_T
#define _UINTPTR_T
typedef unsigned long uintptr_t;
#endif /* _UINTPTR_T */
也就是說(shuō)uintptr_t
也是占用8個(gè)字節(jié)茫打,所以我們只要對(duì)struct
賦值,也可以通過bits
來(lái)訪問這段內(nèi)存妖混。
我們先看看struct的定義(ISA_BITFIELD
在arm64和x86架構(gòu)上有著不同的定義老赤,我們這里只討論arm64):
# 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)
我們可以看到這里總共定義了64位,每一位上都代表的不同的意義制市,下面是這64位的具體含義:
字段名 | 意義 |
---|---|
nonpointer | 表示是否對(duì) isa 指針開啟指針優(yōu)化 0:純isa指針抬旺,1:不止是類對(duì)象地址,isa 中包含了類信息、對(duì)象的引用計(jì)數(shù)等 |
has_assoc | 關(guān)聯(lián)對(duì)象標(biāo)志位祥楣,0沒有开财,1存在 |
has_cxx_dtor | 該對(duì)象是否有 C++ 或者 Objc 的析構(gòu)器,如果有析構(gòu)函數(shù),則需要做析構(gòu)邏輯, 如果沒有,則可以更快的釋放對(duì)象 |
shiftcls | 存儲(chǔ)類指針的值汉柒。開啟指針優(yōu)化的情況下,在 arm64 架構(gòu)中有 33 位用來(lái)存儲(chǔ)類指針责鳍。 |
magic | 用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒有初始化的空間 |
weakly_referenced | 標(biāo)志對(duì)象是否被指向或者曾經(jīng)指向一個(gè) ARC 的弱變量碾褂,沒有弱引用的對(duì)象可以更快釋放。 |
deallocating | 標(biāo)志對(duì)象是否正在釋放內(nèi)存 |
has_sidetable_rc | 當(dāng)對(duì)象引用技術(shù)大于 10 時(shí)历葛,則需要借用該變量存儲(chǔ)進(jìn)位 |
extra_rc | 當(dāng)表示該對(duì)象的引用計(jì)數(shù)值正塌,實(shí)際上是引用計(jì)數(shù)值減 1, 例如恤溶,如果對(duì)象的引用計(jì)數(shù)為 10乓诽,那么 extra_rc 為 9。如果引用計(jì)數(shù)大于 10咒程, 則需要使用到下面的 has_sidetable_rc鸠天。 |
實(shí)際內(nèi)存占用情況如下:
我們看到實(shí)際上shiftcls
才是真正的Class類名,我們前面知道了getIsa
方法的返回:
return (Class)(isa.bits & ISA_MASK);
通過bits
和ISA_MASK
相與孵坚,實(shí)際上就是將shiftcls
所占用的33位數(shù)據(jù)提取出來(lái)粮宛,這也就是isa只返回了Class類名的原因
驗(yàn)證
接下來(lái)我們驗(yàn)證一下是否如上所說(shuō):(驗(yàn)證的時(shí)候是在mac,x86上驗(yàn)證卖宠,和arm64有些不一樣巍杈,但是并不影響結(jié)果)
我們可以通過以下兩種方式來(lái)驗(yàn)證:
- 將對(duì)象的
isa
地址和ISA_MASK
進(jìn)行與運(yùn)算 - 對(duì)對(duì)象的
isa
地址進(jìn)行位運(yùn)算,然后將當(dāng)前類的Class
編碼扛伍,進(jìn)行對(duì)比
通過ISA_MASK
與運(yùn)算
我們先拿到objc2的isa地址筷畦,上圖中紅色框中就是isa地址
查看源碼得知了ISA_MASK
定義為0x0000000ffffffff8ULL
po 0x001d8001000020e9 & 0x0000000ffffffff8ULL
得到結(jié)果:
位運(yùn)算驗(yàn)證
我們知道在isa_t
結(jié)構(gòu)體的定義中,shiftcls
從第四位開始只占用33位(x86架構(gòu)下占用44位)
刺洒,所以在isa的低位還有3位
鳖宾,高位還有27位
(x86架構(gòu)下高位為17位
)
我們先將isa左移3位,抹去低三位數(shù)據(jù):
(lldb) p 0x001d8001000020e9 >> 3
(long) $1 = 1037939513492509
(lldb)
然后右移20位(需要加上上一步左移的三位)抹去高位數(shù)據(jù):
(lldb) p $1 << 20
(long) $2 = 562951057571840
然后再將位置復(fù)原到原來(lái)shiftcls
的第四位:
(lldb) p $2 >> 17
(long) $3 = 4294975720
得到結(jié)果:4294975720
逆航,記住這個(gè)數(shù)字
然后運(yùn)行到objc源碼
此時(shí)我們將cls編碼輸出鼎文,以及將剛才得到的數(shù)字按照16進(jìn)制輸出:
(lldb) p/x (uintptr_t)cls
(uintptr_t) $4 = 0x00000001000020e8
(lldb) p/x 4294975720
(long) $5 = 0x00000001000020e8
(lldb)
得到了一樣的結(jié)果0x00000001000020e8
,進(jìn)一步說(shuō)明isa指針的中間33位
(x86中間為44位)存儲(chǔ)的才是class的類名