前言
相信大部分多年工作經(jīng)驗(yàn)的iOS開(kāi)發(fā)者知道了OC的對(duì)象本質(zhì)上是一個(gè)基于C語(yǔ)言封裝的結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體有一個(gè)成員叫isa,它指向這個(gè)對(duì)象的類(lèi)對(duì)象,那么今天我就來(lái)深入探究下這個(gè)isa成員。
驗(yàn)證對(duì)象是否真的是結(jié)構(gòu)體,結(jié)構(gòu)體是否有isa成員。
1.我們創(chuàng)建一個(gè)工程,在man.m文件內(nèi)定義一個(gè)繼承NSObject的對(duì)象LGPerson,里面放一個(gè)NSString屬性name。如下圖

main.m
2.用Clang編譯器將剛剛編寫(xiě)的main.m文件編譯成C++文件。
用終端cd到剛剛創(chuàng)建的工程根目錄下,然后執(zhí)行clang -rewrite-objc main.m -o main.cpp把目標(biāo)文件編譯成c++文件

編譯成C++
然后我們打開(kāi)main.cpp文件,搜索LGPerson結(jié)果如下,我們發(fā)現(xiàn)了LGPerson真的是個(gè)結(jié)構(gòu)體,下圖紅框標(biāo)注iOS底層聲明了一個(gè)
objc_object結(jié)構(gòu)體LGPerson,我們記住這個(gè)objc_object結(jié)構(gòu)體名稱。
LGPerson
3.我們?cè)趍ain.m文件導(dǎo)入頭文件#import <objc/objc.h>,按住command鼠標(biāo)左鍵點(diǎn)擊頭文件跳轉(zhuǎn)進(jìn)去objc源碼,然后我們搜索objc_object,發(fā)現(xiàn)如下圖所示iOS底層對(duì)象真的是結(jié)構(gòu)體封裝,并且真的里面有一個(gè)isa。

objc文件源碼
isa里面有哪些信息,是不是真的有類(lèi)對(duì)象,還有沒(méi)有其他信息,我們接下來(lái)看看
1.我們繼續(xù)打開(kāi)之前博客提到的objc源碼工程,我們看到紅框里面isa是一個(gè)union聯(lián)合體。
結(jié)構(gòu)體(struct)中所有變量是“共存”的——優(yōu)點(diǎn)是“有容乃大”, 全面;缺點(diǎn)是struct內(nèi)存空間的分配是粗放的,不管用不用,全分配。
聯(lián)合體(union)中是各變量是“互斥”的——缺點(diǎn)就是不夠“包容”; 但優(yōu)點(diǎn)是內(nèi)存使用更為精細(xì)靈活,也節(jié)省了內(nèi)存空間。

isa結(jié)構(gòu)
大家可以具體了解下聯(lián)合體概念,接下來(lái)我們點(diǎn)擊聯(lián)合體
isa里面的ISA_BITFIELD看到這是一個(gè)宏定義
isa
那么上面宏定義里面這些
nonpointer :1是代表什么呢?nonpointer:表示是否對(duì) isa 指針開(kāi)啟指針優(yōu)化 0:純isa指針,1:不止是類(lèi)對(duì)象地址,isa 中包含了類(lèi)信息、對(duì)象的引用計(jì)數(shù)等。has_assoc:關(guān)聯(lián)對(duì)象標(biāo)志位,0沒(méi)有,1存在。has_cxx_dtor:該對(duì)象是否有 C++ 或者 Objc 的析構(gòu)器,如果有析構(gòu)函數(shù),則需要做析構(gòu)邏輯, 如果沒(méi)有,則可以更快的釋放對(duì)象。shiftcls:存儲(chǔ)類(lèi)指針的值。開(kāi)啟指針優(yōu)化的情況下,在 arm64 架構(gòu)中有 33 位用來(lái)存儲(chǔ)類(lèi)指針,上面圖中74行# elif __x86_64__表示在x86_64(模擬器,Mac端)內(nèi)核下占44位。magic:用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒(méi)有初始化的空間。weakly_referenced:志對(duì)象是否被指向或者曾經(jīng)指向一個(gè) ARC 的弱變量,沒(méi)有弱引用的對(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。isa占8字節(jié)64位,第一個(gè)
nonpointer :1,表示從右開(kāi)始長(zhǎng)度為1的字節(jié)位數(shù)信息為nonpointer同理第四排
shiftcls :44表示從第4位開(kāi)始往左長(zhǎng)度為44位存儲(chǔ)類(lèi)指針好了,看完資料我們了解了isa里面第4位到47位存儲(chǔ)了類(lèi)指針,接下來(lái)就是驗(yàn)證環(huán)節(jié)了,注意我們是在模擬器運(yùn)行,如果是真機(jī)運(yùn)行那么
shiftcls就只占33位了。
image.png
我們打個(gè)斷點(diǎn)然后在控制臺(tái)按照紅框的指令打印LGPerson對(duì)象p的數(shù)據(jù),最后拿到p的首內(nèi)存地址數(shù)據(jù)
0x001d800100002205(也就是isa字段),然后我們通過(guò)輸入p/t LGPerson.class打印LGPerson類(lèi)所存儲(chǔ)的2進(jìn)制地址。
isa內(nèi)4-47位數(shù)據(jù)和LGPerson類(lèi)存儲(chǔ)指針2進(jìn)制地址
我們發(fā)現(xiàn)一個(gè)驚人的事實(shí),那就是我紅框內(nèi)的數(shù)據(jù)一模一樣!從右起第4位開(kāi)始到第47位中間44位數(shù)據(jù)一模一樣!這個(gè)完全驗(yàn)證了我們上面貼圖的
isa內(nèi)的shiftcls : 44;的定義。
結(jié)論
對(duì)象其實(shí)是底層封裝好的結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)第一個(gè)成員為isa(這個(gè)對(duì)象所繼承的類(lèi)指針),其中該類(lèi)指針?lè)旁趇sa的第4-47位(x86_64 模擬器)或第4-37位(arm64 真機(jī))