iOS開發(fā)者一定知道每個(gè)實(shí)例對象都有一個(gè)isa指針徐伐,其中存儲著對象的類信息。今天我們就來探究下isa是如何保存類的信息的女嘲。
通過objc的源碼可以找到我們在調(diào)用alloc方法創(chuàng)建實(shí)例對象的時(shí)候有初始化isa的操作畜份,其初始化代碼如下
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#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
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
isa = newisa;
}
由上述代碼可知,isa其實(shí)是isa_t類型的結(jié)構(gòu)體欣尼,那么isa_t又是什么呢爆雹?
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
從isa_t的定義中引出了一個(gè)新的數(shù)據(jù)類型--聯(lián)合體
。我們來看下聯(lián)合體和結(jié)構(gòu)體有什么區(qū)別:
結(jié)構(gòu)體
結(jié)構(gòu)體是把不同的數(shù)據(jù)組合成一個(gè)整體,其變量是共存的顶别,不管變量是否使用,在創(chuàng)建時(shí)都會(huì)分配內(nèi)存拒啰。
-
優(yōu)點(diǎn):
存儲容量大驯绎,包容性強(qiáng)
,且成員之間不會(huì)相互影響
谋旦。 -
缺點(diǎn):所有變量都會(huì)分配內(nèi)存剩失,
內(nèi)存消耗較大
。
聯(lián)合體
聯(lián)合體也是由不同的數(shù)據(jù)類型組成册着,但其變量是互斥
的拴孤,所有的成員共占一段內(nèi)存,而且聯(lián)合體采用了內(nèi)存覆蓋技術(shù)甲捏,同一時(shí)刻只能保存一個(gè)成員變量的值
演熟,如果對新的成員賦值,就會(huì)將原來的成員的值覆蓋掉司顿。
-
優(yōu)點(diǎn):所有成員共用一段內(nèi)存芒粹,使
內(nèi)存的使用更為精細(xì)靈活
,同時(shí)也節(jié)省了內(nèi)存空間
大溜。 -
缺點(diǎn):
包容性差
化漆。
我們再回過來看isa_t的定義,其定義了兩個(gè)變量一個(gè)為bits钦奋,另一個(gè)為cls
座云,同時(shí)如果定義了ISA_BITFIELD
的情況下,定義了位域結(jié)構(gòu)體
以便我們精確的操作isa_t中的不同位的數(shù)據(jù)付材。ISA_BITFIELD的定義根據(jù)不同的cpu進(jìn)行了不同的定義朦拖,定義的字段和內(nèi)容大同小異,只是不同的字段占用的位數(shù)量不一樣厌衔,本文僅以x86_64為例說明:
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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
如上所示就是ISA_BITFIELD在x86_64下的定義贞谓,其中:
nonpointer
:存儲于第0位,標(biāo)識是否為優(yōu)化isa標(biāo)志
葵诈。0代表不是優(yōu)化的isa裸弦,此isa是一個(gè)純指向類或者原類的指針;1標(biāo)識是優(yōu)化的isa作喘,此isa中包含類信息理疙,對象的引用計(jì)數(shù)等。我們現(xiàn)在創(chuàng)建的對象中的isa基本上都是優(yōu)化后的isa
泞坦。has_assoc
:存儲于第1位窖贤,關(guān)聯(lián)對象標(biāo)志位
。對象含有或者曾經(jīng)含有關(guān)聯(lián)引用。0表示沒有赃梧;1表示有滤蝠。沒有關(guān)聯(lián)引用的對象可以更快的釋放內(nèi)存。has_cxx_dtor
:存儲于第3位授嘀,析構(gòu)函數(shù)標(biāo)志位
物咳,如果有析構(gòu)函數(shù),則需進(jìn)行析構(gòu)邏輯蹄皱,如果沒有览闰,則可以更快速的釋放對象。shiftcls
:存儲于第3-46位巷折,存儲類的信息压鉴,這個(gè)是我們主要關(guān)注的內(nèi)容
。magic
:存儲于第47-52位锻拘,判斷對象是否初始化完成
油吭,是調(diào)試器判斷當(dāng)前對象是真的對象還是沒有初始化的空間。weakly_referenced
:存儲于第53位署拟,標(biāo)識對象是否被指向或者曾經(jīng)被指向一個(gè)ARC的弱引用變量
上鞠。如果沒有弱引用的對象可以更快的被釋放。unused
:存儲于第54位芯丧。has_sidetable_rc
:存儲于第55位芍阎,判斷該對象的引用計(jì)數(shù)是否過大
,如果過大則需要其他散列表來進(jìn)行存儲缨恒。-
extra_rc
:存儲于第56-63位谴咸,存放該對象引用計(jì)數(shù)值-1的結(jié)果
。
了解了isa的數(shù)據(jù)類型之后我們看下objc是如何設(shè)置isa的呢骗露?根據(jù)objc源碼我們找到了設(shè)置isa的具體方法initIsa岭佳,代碼如下:
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#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
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
isa = newisa;
}
在實(shí)際開發(fā)中,我們創(chuàng)建的類基本上都是nonpointer的萧锉,故此處isa的創(chuàng)建會(huì)走else中的代碼珊随,在此處打上斷點(diǎn)后我們一步一步跟斷點(diǎn)。
經(jīng)過ISA_MAGIC_VALUE
初始化newisa.bit
之后柿隙,我們看到newisa中bits的值變?yōu)榱?303511812964353
叶洞,而ISA_MAGIC_VALUE轉(zhuǎn)為10進(jìn)制數(shù)也是8303511812964353,在計(jì)算器中輸入后可以看到2進(jìn)制計(jì)數(shù)法中每一位的情況禀崖,如下所示
在newisa
的位信息中衩辟,除了nonpointer為1與計(jì)算器中的第0位的1相符之外,magic被置為了59
波附,同時(shí)計(jì)算器中從第47位開始(即magic的首位)顯示為110111
艺晴,此時(shí)我們將59輸入計(jì)算器中發(fā)現(xiàn)59的二進(jìn)制計(jì)數(shù)正是110111昼钻,與bit中的值相等
。
下一步是setClass封寞,我們進(jìn)入setClass方法后發(fā)現(xiàn)設(shè)置shiftcls
的代碼就一行然评。
shiftcls = (uintptr_t)newCls >> 3;
此處將class右移了3位
存入了shiftcls中,這是因?yàn)?code>class信息的最后4位始終是0狈究,即無效位挤巡,為了匹配shiftcls是從第3位開始存儲的钟病,便去除了最右邊三位無效位存入了shiftcls中
授翻。
在setClass之后shiftcls的信息變成了536875037去扣,那么此時(shí)shiftcls中存儲的是否是SLPerson的類信息呢风喇?
我們通過以下幾種方式來看下:
- 我們剛剛說了class的最后三位是0宁改,所以我們只需要將isa中除了shiftcls的其它位置0便可以得到正確的類信息,即我們可以采取
右移3位魂莫,左移20位还蹲,再右移17位
的方式將其余位全部置0。
(lldb) p newisa.bits >> 3
(uintptr_t) $53 = 1037939513495581
(lldb) p $53 << 20
(uintptr_t) $54 = 562954278797312
(lldb) p $54 >> 17
(uintptr_t) $55 = 4295000296
(lldb) po $55
SLPerson
通過如上打印可以看到我們將其余位全部置0后得到的信息就是SLPerson耙考。
這是我們直接通過位運(yùn)算來還原了isa中類的信息谜喊,那么系統(tǒng)又是如何獲取類的信息的呢?
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA();
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
inline Class
objc_object::ISA(bool authenticated)
{
ASSERT(!isTaggedPointer());
return isa.getDecodedClass(authenticated);
}
inline Class
isa_t::getDecodedClass(bool authenticated) {
#if SUPPORT_INDEXED_ISA
if (nonpointer) {
return classForIndex(indexcls);
}
return (Class)cls;
#else
return getClass(authenticated);
#endif
}
inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
return cls;
#else
uintptr_t clsbits = bits;
clsbits &= ISA_MASK;
return (Class)clsbits;
#endif
}
# define ISA_MASK 0x00007ffffffffff8ULL
從源碼可知倦始,獲取class的方法最終就是一行代碼return bits & ISA_MASK
斗遏,我們將ISA_MASK
放入計(jì)算器中發(fā)現(xiàn)就是一個(gè)3-46位為1,其余位為0的二進(jìn)制數(shù)
鞋邑。與bits按位進(jìn)行&運(yùn)算最終的結(jié)果和我們進(jìn)行位移運(yùn)算是一樣的~