今天的主題是探索isa
的結構 在此之前我們需要先了解下什么是聯(lián)合體
構造數(shù)據(jù)類型的方式有以下兩種 :
-
結構體
(struct
) -
聯(lián)合體
(union
,也稱為共用體
)
結構體
結構體是指把不同的數(shù)據(jù)組合成一個整體
,其變量是共存的咖杂,變量不管是否使用滤馍,都會分配內存狂打。
缺點:所有屬性都分配內存,比較
浪費內存
僚碎,假設有4
個int成員相满,一共分配了16
字節(jié)的內存,但是在使用時,你只使用了4字節(jié)诗箍,剩余的12字節(jié)就是屬于內存的浪費優(yōu)點:存儲
容量較大
癣籽,包容性強
,且成員之間不會相互影響
聯(lián)合體
聯(lián)合體也是由不同的數(shù)據(jù)類型組成
滤祖,但其變量是互斥
的筷狼,所有的成員共占
一段內存。而且共用體采用了內存覆蓋技術
匠童,同一時刻只能保存一個成員的值埂材,如果對新的成員賦值,就會將原來成員的值覆蓋
掉
缺點:汤求,包容性弱
優(yōu)點:所有成員共用一段內存俏险,使內存的使用更為精細靈活严拒,同時也節(jié)省了內存空間
兩者的區(qū)別
內存占用情況
結構體的各個成員會
占用不同的內存
,互相之間沒有影響
共用體的所有成員
占用同一段內存
竖独,修改一個成員會影響其余所有成員內存分配大小
結構體內存 >= 所有成員占用的
內存總和
(成員之間可能會有縫隙)共用體占用的內存等于
最大
的成員占用的內存
現(xiàn)在我們已經(jīng)大致了解什么是聯(lián)合體,下面進行isa
探索,上代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *objc = [Person alloc];
NSLog(@"Hello, World! %@",objc);
}
return 0;
}
從alloc
的源碼跟進去到關聯(lián)指針和類的步驟:
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
跟進obj->initInstanceIsa(cls, hasCxxDtor);
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
主要做的事情是initIsa(cls, true, hasCxxDtor);
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls); // isa 初始化
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0); // isa 初始化
#if SUPPORT_INDEXED_ISA // !nopointmenter 執(zhí)行的流程 ,即isa通過cls定義
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 //bits 時執(zhí)行的流程
newisa.bits = ISA_MAGIC_VALUE;//bist進行賦值 為0x001d800000000001ULL
// 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
條件是否滿足裤唠,都會生成一個isa_t
的類型。跟進去可以發(fā)現(xiàn):
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
類型使用聯(lián)合體的原因也是基于內存優(yōu)化的考慮莹痢,這里的內存優(yōu)化是指在isa指針中通過char + 位域
(即二進制中每一位均可表示不同的信息)的原理實現(xiàn)种蘸。通常來說,isa指針占用的內存大小是8
字節(jié)竞膳,即64位航瞭,已經(jīng)足夠存儲很多的信息了,這樣可以極大的節(jié)省內存坦辟,以提高性能
從isa_t
的定義中可以看出:
提供了兩個成員刊侯,cls
和bits
,由聯(lián)合體的定義所知长窄,這兩個成員是互斥
的滔吠,也就意味著,當初始化isa
指針時挠日,有兩種初始化方式
通過cls
初始化疮绷,bits
無默認值
通過bits
初始化,cls
有默認值
還提供了一個結構體定義的位域嚣潜,用于存儲類信息及其他信息冬骚,結構體的成員ISA_BITFIELD
,這是一個宏定義
懂算,有兩個版本 __arm64__
(對應ios 移動端) 和__x86_64__
(對應macOS)只冻,以下是它們的一些宏定義,如下圖所
其中存儲的成員信息:
-
nonpointer
一般自定義的類都是這個類型的计技,而系統(tǒng)類才會有純isa指針的情況喜德,占1位。
0:純isa
指針垮媒。
1:不只是類對象地址舍悯,isa中包含了類信息、對象的引用計數(shù)等睡雇。 -
has_assoc
關聯(lián)對象標志位萌衬,0代表沒有關聯(lián)對象,1代表存在關聯(lián)對象它抱,占1位秕豫。 -
has_cxx_dtor
該對象是否有C++或OC的析構器,如果有析構函數(shù)观蓄,則需要做析構邏輯混移,如果沒有祠墅,則可以更快釋放對象,占1位沫屡。 -
shiftclx
存儲類指針的值饵隙, 也就是類信息,開啟指針優(yōu)化的情況下沮脖,在arm64
架構中有33位用來存儲類指針金矛,`x86_64``架構中占44位。 -
magic
用于調試器判斷當前對象是真的對象還是沒有初始化的空間勺届,占6位驶俊。 -
weakly_refrenced
是指對象是否被指向或者曾經(jīng)指向一個ARC的弱變量,沒有弱引用的對象可以更快釋放免姿。 -
deallocating
標志對象是否正在釋放內存饼酿。 -
has_sidetable_rc
當對象引用計數(shù)大于2的19次方(x86_64架構為2的8次方)時,則需要存儲到散列表
胚膊,這時該變量值變?yōu)?code>true故俐。 -
extra_rc
表示該對象的``引用計數(shù)值,最大為2的19次方(x86_64架構為2的8次方)紊婉,實際上是引用計數(shù)值減1药版,,如果大于最大容量喻犁,就需要取一半計數(shù)存到散列表中槽片,真機上最多有8張散列表存儲對象引用計數(shù),x86_64則最多64張肢础,這時上面的has_sidetable_rc
值置為true还栓。
針對兩種不同平臺,其isa
的存儲情況如圖所示
下面驗證alloc
過程中isa
是如何跟類
進行關聯(lián)的
上面 initIsa
方法的源碼實現(xiàn)我們可以發(fā)現(xiàn)邏輯主要分為兩部分
- 通過 cls 初始化 isa
- 通過 bits 初始化 isa
運行源碼 斷點如圖 打印newisa
信息
-繼續(xù)執(zhí)行传轰,到newisa.bits = ISA_MAGIC_VALUE;
下一行剩盒,表示為isa的bits成員賦值,重新執(zhí)行l(wèi)ldb命令p newisa
慨蛙,得到的結果如下
對比發(fā)現(xiàn)數(shù)據(jù)發(fā)生變化,可以看到賦值后的nonpointer
已經(jīng)是1了辽聊,magic
為59。
- 其中
magic
是59
是由于將isa指針地址轉換為二進制股淡,從47
(因為前面有4
個位域,共占用47
位廷区,地址是從0開始)位開始讀取6位唯灵,再轉換為十進制,如下圖所示
此時此刻隙轻,可以得出結果埠帕,這magic的59確實是由0x001d800000000001ULL填進去的垢揩。
繼續(xù)執(zhí)行 過newisa.shiftcls = (uintptr_t)cls >> 3;
這一步 打印newisa
-
shiftcls
由0
變成了536871965
即為(uintptr_t)cls >> 3
為什么在shiftcl
s賦值時需要類型強轉
?
因為內存的存儲不能存儲字符串
敛瓷,機器碼只能識別0 叁巨、1
這兩種數(shù)字,所以需要將其轉換為uintptr_t
數(shù)據(jù)類型呐籽,這樣shiftcls
中存儲的類信息才能被機器碼理解锋勺, 其中uintptr_t是long
為什么需要右移3位
?
主要是由于shiftcls
處于isa
指針地址的中間
部分狡蝶,前面還有3
個位域庶橱,為了不影響前面的3
個位域
的數(shù)據(jù),需要右移將其抹零
進一步驗證類與指針對象
相關聯(lián)
- 控制臺打印這個
obj
指針贪惹,并將isa
的內存地址右移3位
苏章,再左移20位
,再右移17位
奏瞬,這時再打印地址移動之后的is
a的地址枫绅,可以看到,就是我們上面關聯(lián)的類硼端,也就是說這時對象指針和類關聯(lián)上了, 如圖
- 控制臺打印這個
- 通過
isa & ISA_MSAK
如圖
- 通過