前言
在上一篇的文章iOS alloc 流程分析中我們分析了alloc
杨凑,知道了alloc
創(chuàng)建了對象并且分配內(nèi)存鸿染,同時初始化isa
屬性。我們也知道了Objective-C 對象在底層本質(zhì)上是結(jié)構(gòu)體石洗,所有的對象里面都會包含有一個isa
,這篇文章我們來分析探究isa底層是如何實(shí)現(xiàn)的
。
目錄
一锁荔、isa的初始化
//_class_createInstanceFromZone的部分代碼
//初始化實(shí)例的isa指針
obj->initInstanceIsa(cls, hasCxxDtor);
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {// 上一個方法傳入的nonpointer為true![![![WX20200910-165320.png](https://upload-images.jianshu.io/upload_images/2987980-7afa02c886094fc9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
](https://upload-images.jianshu.io/upload_images/2987980-9e9fe9cabbde3a9f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
](https://upload-images.jianshu.io/upload_images/2987980-0feda4063f97c6de.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
isa = isa_t((uintptr_t)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
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
其中的nonpointer
字面的意思是沒有指針的蝙砌,一般情況下nonpointer是為true的阳堕,只有在例如實(shí)現(xiàn)了allocwithzone
方法,retain择克,release等的時候會是false恬总。如果為false是直接將傳進(jìn)來的cls為isa的關(guān)聯(lián)的cls賦值。
其他的剩下的部分就是對isa的初始化賦值了肚邢。但是具體的isa內(nèi)部是怎樣的還是不知道的壹堰,從源碼中isa_t
點(diǎn)擊進(jìn)去可以查看。
二骡湖、 isa的內(nèi)部結(jié)構(gòu)
通過源碼可以知道isa
的isa_t
類型的內(nèi)部結(jié)構(gòu)
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
};
typedef unsigned long uintptr_t;
從中可以知道贱纠,isa
是一個聯(lián)合體 union
,里面有關(guān)聯(lián)的類cls和long型的bits响蕴。
什么是
聯(lián)合體(union)
呢谆焊?聯(lián)合體是一種特殊的類,也是一種構(gòu)造類型的數(shù)據(jù)結(jié)構(gòu)浦夷。完全就是共用一個內(nèi)存首地址辖试,并且各種變量名都可以同時使用辜王,操作也是共同生效。所以也叫共用體
剃执。并且聯(lián)合體(union)中是各變量是“互斥
”的誓禁,但是內(nèi)存使用更為精細(xì)靈活,也節(jié)省了內(nèi)存空間肾档。
聯(lián)合體(union)摹恰、位域不清楚的可以參考這篇文章
由上面的概念可以知道,cls
和bits
之間是互斥的怒见,即有cls就沒有bits,有bits就沒有cls俗慈。這就很好地解釋了為什么上面的源碼在初始化isa的時候會用nonpointer來區(qū)分開。所以isa的大小占8個字節(jié)遣耍,64位闺阱。其中這64位中分別存儲了什么呢?通過ISA_BITFIELD位域源碼:
# 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)
這兩種是分別arm64
和x86
系統(tǒng)架構(gòu)下isa的內(nèi)部結(jié)構(gòu)舵变,但是都是64位的酣溃。iPhone 采用arm64
架構(gòu),MacOS 采用x86
架構(gòu)纪隙,本文介紹x86
架構(gòu)下isa的聯(lián)合體結(jié)構(gòu)
赊豌。
-
nonpointer
:占1位,表示是否對isa指針
開啟指針優(yōu)化.
0:不優(yōu)化绵咱,是純isa指針
碘饼,當(dāng)訪問isa指針
時,直接返回其成員變量cls
1:優(yōu)化悲伶,即isa 指針
內(nèi)容不止是類地址艾恼,還包含了類的一些信息、對象的引用計(jì)數(shù)等麸锉。 -
has_assoc
:占1位钠绍,表示是否有關(guān)聯(lián)對象,0沒有花沉,1存在五慈。 -
has_cxx_dtor
:占1位,表示該對象是否有C++
或者Objc
的析構(gòu)器主穗,如果有析構(gòu)函數(shù)泻拦,則需要做析構(gòu)邏輯,如果沒有則可以更快的釋放對象忽媒。 -
shiftcls
:存儲類指針的值争拐。開啟指針優(yōu)化的情況下,在x86_64 架構(gòu)
有 44位 用來存儲類指針地址,在arm64架構(gòu)
中有33位用來存儲類指針地址。(由上面的初始化源碼也可以很好的說明,關(guān)聯(lián)的類指針向右移3位)
newisa.shiftcls = (uintptr_t)cls >> 3;
-
magic
:占6位架曹,用于調(diào)試器判斷當(dāng)前對象是真的對象還是沒有初始化的空間隘冲。 -
weakly_referenced
:占1位,表示對象是否被指向或者曾經(jīng)指向一個ARC
的弱變量绑雄,沒有弱引用的對象可以更快釋放展辞。 -
deallocating
:占1位,表示對象是否正在釋放內(nèi)存万牺。 -
has_sidetable_rc
:占1位罗珍,表示當(dāng)對象引用技術(shù)大于10時,則需要借用該變量存儲進(jìn)位脚粟。 -
extra_rc
:占8位覆旱,表示該對象的引用計(jì)數(shù)值,實(shí)際上是引用計(jì)數(shù)總值減1核无。例如扣唱,如果對象的引用計(jì)數(shù)為10,那么extra_rc
為9团南,如果引用計(jì)數(shù)大于10噪沙,則需要使用到上面的has_sidetable_rc
。
isa 在arm64
架構(gòu)和x86
架構(gòu)中的內(nèi)存布局
三吐根、isa是對象與類的連接
通過objc4-781
蘋果官方的源碼正歼,使用object_getClass這個方法獲取到類。
NAPerson *objc = [NAPerson alloc];
Class testClass = object_getClass(objc);
NANSLog(@"%@",testClass);//NAPerson
通過源碼找到object_getClass的方法
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()
{
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
}
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
define ISA_MASK 0x00007ffffffffff8ULL
從源碼中可以知道返回的isa
最終是(Class)(isa.bits & ISA_MASK)
其中源碼有一個判斷isTaggedPointer()
,其中蘋果對于Tagged Pointer
的概念引入佑惠,是為了節(jié)省內(nèi)存和提高執(zhí)行效率,對于 64 位程序齐疙,相關(guān)邏輯能減少一半的內(nèi)存占用膜楷,以及 3 倍的訪問速度提升,100 倍的創(chuàng)建贞奋、銷毀速度提升赌厅。如果想了解這部分的內(nèi)容可以看看深入理解 Tagged Pointer
下面就是用lldb
的指令來驗(yàn)證一下的。通過x/4gx objc
打印出對象的內(nèi)存地址
由源碼知道類Class的最終返回是(Class)(isa.bits & ISA_MASK)
轿塔,所以將x/4gx objc
打印出來的0x001d800100002119 & ISA_MASK
的值得到如下:
由前面的文章知道由于內(nèi)存的優(yōu)化對象的其他屬性的位置實(shí)際會發(fā)生變化的特愿,所以對象的第一個屬性就是 isa
通過測試我們發(fā)現(xiàn)對象的內(nèi)存地址
和通過isa取出來的內(nèi)存地址
是一樣的,所以isa
是關(guān)聯(lián)著對象與類的
注意:對 shiftcls 的分析是建立在 nonpointer 為 1 的情況下勾缭,如果 nonpointer 為0揍障,整個 isa 存儲的是 cls 。
四俩由、isa的走位原理
通過上面的介紹毒嫡,可以知道了isa是關(guān)聯(lián)著對象與類的,并且對象的isa指向類幻梯,因?yàn)槿f物皆對象兜畸,那么類的isa指向的是誰呢努释?可以通過蘋果官方的isa的走位流程圖:
其中虛線是代表isa
的走位,實(shí)線代表的是繼承關(guān)系
的走位咬摇。
通過以上的源碼分析伐蒂,我們認(rèn)識到對象的isa指針
指向了對象所屬的類。而類本身也有一個isa指針
肛鹏,它指向的又是什么呢逸邦?
此時要引入meta class
(即元類)的概念了。我們先了解一下元類的信息:
在
OC
中龄坪,對象的方法并沒有存儲于對象的結(jié)構(gòu)體中(如果每一個對象都保存了自己能執(zhí)行的方法昭雌,那么對內(nèi)存的占用有極大的影響)。
當(dāng)對象的實(shí)例方法被調(diào)用時健田,它通過自己的isa
來查找對應(yīng)的類烛卧,然后在所屬類的class_data_bits_t
結(jié)構(gòu)體中查找對應(yīng)方法的實(shí)現(xiàn)。同時妓局,每一個objc_class
也有一個指向自己的父類的指針superclass
用來查找繼承的方法总放。
而當(dāng)調(diào)用 類方法 時,它的查找流程是怎樣的呢好爬?對此OC
的解決方案就是引入元類局雄,來保證類方法也能通過相同的機(jī)制查找到。也就是說存炮,類的isa
指向的是元類炬搭。
UIView
實(shí)際上也是一個對象,叫做類對象
穆桂,那么類對象的類又是什么呢宫盔?就是元類
。元類也是一個對象享完,它的類又是什么呢灼芭?根元類
。根元類也是一個對象般又,它的類又是什么呢彼绷?它自己
。
元類對象:OC
的類方法
是元類
存在的根本原因茴迁。因?yàn)?code>元類對象中存儲著類對象調(diào)用的方法也就是類方法
寄悯。元類的定義和創(chuàng)建都是編譯器自動完成的谐区,無需人為干涉同波,而且大部分時候都是傾向于隱藏的害晦。
驗(yàn)證過程見參考文章
isa
指向結(jié)論
對象的
isa指針
指向 對象的所屬類(如person
對象的isa
指向Person
類)類的
isa指針
指向 類的元類(如Person
類的isa
指向Person元類
)元類的
isa指針
指向 根元類(如Person元類
的isa
指向NSObject元類
)NSObject類
的元類是根元類
NSObject元類
的isa指針
指向自身
(是個圓圈)
四、 isa總結(jié)
-
isa
是isa_t
結(jié)構(gòu)镣煮,采用 聯(lián)合體+位域 的搭配來設(shè)計(jì):在不同的位上顯示不同的內(nèi)容排苍,以此來節(jié)省儲存空間五芝,進(jìn)而優(yōu)化內(nèi)存猛频。
2.isa
包含了cls
和bits
兩個成員變量,這兩個成員變量在64位CPU
架構(gòu)下的長度都是8字節(jié)
秘遏,所以isa
在64位CPU
架構(gòu)下的長度也是8字節(jié)
丘薛。 -
isa
的位域上存儲了一些對象與類的信息,并將對象與類關(guān)聯(lián)起來邦危,起到中間橋梁的作用洋侨。 -
is
a指向圖相關(guān)結(jié)論:- 對象的
isa指針
指向 對象的所屬類(如person
對象的isa
指向Person類
) - 類的
isa指針
指向 類的元類(如Person
類的isa
指向Person元類
) - 元類的
isa指針
指向 根元類(如Person元類
的isa
指向NSObject元類
)
根元類的isa指針
指向自身(是個圓圈) - 元類的繼承關(guān)系向上傳遞(如
Teacher元類
繼承自Person元類
)
根元類
繼承自根類
根類
繼承自nil
- 對象的
五、補(bǔ)充
5.1 如果不用ISA_MASK
倦蚪,那么如何證明isa
關(guān)聯(lián)了對象和類的作用呢希坚?
具體思路是,shiftcls
在x86_64
架構(gòu)下長度是44位陵且,存儲在isa
的 [3, 46]位上裁僧,所以可以通過將isa的 [0, 2]位、[47, 63]位清零慕购,同樣能得到shiftcls
的值聊疲,進(jìn)而確定類。(先右移3位沪悲、再左移20位获洲、然后右移17位即可)
isa能保存保存很多數(shù)據(jù),但需要取出一個數(shù)據(jù)則需要將其他不相關(guān)位上置0殿如,然后整個64位的isa才能表示一個數(shù)據(jù)贡珊。
5.2 NSProxy的isa指向
如果Person
類繼承的是NSProxy
,相關(guān)isa
指向是怎樣的呢涉馁?
答案:跟NSObject
一樣门岔,兩者都是根類
。