在開始分析isa
前盆赤,我們得先搞清楚一個問題:對象是什么熔恢?即對象的本質(zhì)是什么筑累?要搞清這個問題我們還得先了解一下Clang
犁珠。
一、Clang
1.Clang是什么
Clang
是?個由Apple
主導(dǎo)編寫互亮,基于LLVM
的C/C++/Objective-C
編譯器犁享。
2.Clang的使用
Clang
的使用語法有很多,這里就不一一舉例了豹休,有興趣的可以自行搜索炊昆,今天我們用到的語法如下:
//將目標(biāo)文件編譯成c++文件
clang -rewrite-objc main.m -o main.cpp
同時Xcode
在安裝的時候順帶安裝了xcrun
命令,xcrun
命令在clang
的基礎(chǔ)上進(jìn)?了?些封裝威根,要更好??些凤巨,用法如下:
//模擬器
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o
main-arm64.cpp
//手機(jī)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main?arm64.cpp
簡單了解完Clang
,我們來使用看看吧洛搀。
二敢茁、對象的本質(zhì)
1.使用上述clang
語法,我們得到了一個cpp
文件留美,如下圖:
LGPerson
卷要,看一下編譯前后有什么不同,如下圖:
LGPerson
變成了一個結(jié)構(gòu)體(struct)
僧叉,并且定義的屬性name
變成了結(jié)構(gòu)體
的數(shù)據(jù)成員
,同時還多了一個struct NSObject_IMPL NSObject_IVARS
棺榔,并且也是一個結(jié)構(gòu)體
瓶堕。
4.LGPerson
繼承自NSObject
,它在編譯后必然也是一個結(jié)構(gòu)體
症歇,如下圖:
NSObject
結(jié)構(gòu)體作為LGPerson
的第一個數(shù)據(jù)成員
,并且NSObject
中有一個isa
數(shù)據(jù)成員忘晤,這個屬于偽繼承
宛蚓,意味著LGPerson
擁有NSObject
中的所有成員,所以NSObject_IVARS
就等效于isa
设塔。
總結(jié):對象的本質(zhì)
其實就是結(jié)構(gòu)體
凄吏,LGPerson
中的isa
繼承自NSObject
的isa
。
三闰蛔、isa分析
在alloc探索中痕钢,核心三步中有一個initInstanceIsa
方法,進(jìn)入源碼序六,發(fā)現(xiàn)調(diào)用的是initIsa
方法任连,如下圖:
isa
是通過isa_t
類型初始化的例诀,那isa_t
又是什么類型的呢随抠?帶著疑問裁着,我們進(jìn)一步探索,發(fā)現(xiàn)isa_t
是一個聯(lián)合體(union)
拱她,那聯(lián)合體
又是什么呢跨算?
1.聯(lián)合體(union)
聯(lián)合體(union)
也叫共用體
和結(jié)構(gòu)體(struct)
一樣都是構(gòu)造數(shù)據(jù)類型。
結(jié)構(gòu)體
所有變量
都是共存
的椭懊,它們占用不用的內(nèi)存
诸蚕,優(yōu)點:容量大
、成員間互不影響
氧猬;缺點:不管用不用背犯,都會為成員
分配內(nèi)存,浪費內(nèi)存
盅抚。
聯(lián)合體
所有變量
都是互斥
的漠魏,它們共用一段內(nèi)存
,優(yōu)點:節(jié)省內(nèi)存空間
妄均;缺點:包容性弱
柱锹,共用體
使用了內(nèi)存覆蓋技術(shù)
,同一時刻只能保存一個成員的值
丰包,即如果對新的成員賦值
禁熏,就會把原來成員的值覆蓋掉
。
2.isa_t
isa_t
的結(jié)構(gòu)如下圖:
聯(lián)合體
中定義了兩個成員cls
和bits
和一個結(jié)構(gòu)體位域ISA_BITFIELD
(用來存放類信息
和其他信息
)瞧毙。
cls
和bits
說明這里有兩種初始化方式:
1.通過cls
初始化:即isa的初始化
圖中的isa = isa_t((uintptr_t)cls)
2.通過bits
初始化:即else
部分
3.位域
有些數(shù)據(jù)在存儲時并不需要占用一個完整的字節(jié),只需要占用一個或幾個二進(jìn)制位即可寄症≈姹耄基于這種的數(shù)據(jù)結(jié)構(gòu),就是位域
有巧。
這里ISA_BITFIELD
就是一個位域
释漆,它有兩個版本,分別對應(yīng)__arm64__
和__x86_64__
篮迎,即iOS
和macOS
男图,如下圖:
nonpointer
:表示是否對 isa 指針開啟指針優(yōu)化0
:純isa指針1
:不?是類對象地址
,isa 中包含了類信息
柑潦、對象的引?計數(shù)
等
2.has_assoc
:是否關(guān)聯(lián)對象標(biāo)志位
0
:沒有
1
:存在
3.has_cxx_dtor
:該對象是否有 C++ 或者 Objc 的析構(gòu)器
如果有
析構(gòu)函數(shù)(類似于OC中的dealloc
),則需要做析構(gòu)邏輯
,
如果沒有
,則可以更快的釋放對象
4.shiftcls
: 存儲類指針的值
arm64
:開啟指針優(yōu)化的情況下享言,在 arm64 架構(gòu)中有33
位?來存儲類指針
x86_64
:有44
位用來存儲類指針
5.magic
:?于調(diào)試器判斷當(dāng)前對象是真的對象
還是沒有初始化的空間
6.weakly_referenced
:對象是否被指向
或者曾經(jīng)指向?個ARC的弱變量
沒有弱引?
的對象可以更快釋放
7.deallocating
:標(biāo)志對象是否正在釋放內(nèi)存
8.has_sidetable_rc
:當(dāng)對象引?技術(shù)?于10
時,則需要借?該變量存儲進(jìn)位
9.extra_rc
:當(dāng)表示該對象的引?計數(shù)值
渗鬼,實際上是引?計數(shù)值減 1
如果對象的引?計數(shù)為 10
,那么 extra_rc
為 9
如果引?計數(shù)?于 10
荧琼,則需要使?到has_sidetable_rc
isa
的存儲分布如下圖:
4.分析
我們跟隨代碼來到initIsa
方法中譬胎,如下圖:
nonpointer
參數(shù)為true
差牛,這里執(zhí)行了else
的操作,創(chuàng)建了newisa
聯(lián)合體堰乔,打印結(jié)果如下圖:
ISA_MAGIC_VALUE
給bits
進(jìn)行賦值偏化,看一下賦值后的結(jié)果,如下圖:
cls
賦值0x001d800000000001
侦讨,因為在給bits
賦值時,會為cls
追加默認(rèn)值
苟翻,但是為什么magic
賦值了59
呢?
打開計算器并輸入0x001d800000000001
韵卤,從47
位開始讀取6
位,將59
轉(zhuǎn)二進(jìn)制
崇猫,發(fā)現(xiàn)都為111011
沈条,如下圖:
二進(jìn)制
蜡歹,并從47
位開始讀取6
位,再轉(zhuǎn)換成十進(jìn)制
涕烧。但是為什么是從47
開始呢月而?
根據(jù)賦值后的newisa
圖,前面有4個位域
(nonpointer
占1位议纯,has_assoc
占1位景鼠,has_cxx_dtor
占1位,shiftcls
占44位)共占了47位
痹扇,所以magic
從47
位開始铛漓。
3.通過newisa.shiftcls = (uintptr_t)cls >> 3
關(guān)聯(lián)類信息。
shiftcls
已經(jīng)賦上了右移后得到的值536871986
浓恶,同時cls
也從默認(rèn)值
變成了LGPerson
,實現(xiàn)了isa與類的關(guān)聯(lián)
结笨。
這里需要注意兩點:
為什么shiftcls需要強轉(zhuǎn)類型包晰?
因為此時,cls
可能是個字符串
炕吸,直接存儲會導(dǎo)致無法識別伐憾,所以強轉(zhuǎn)成uintptr_t
(unsigned long
類型)
為什么需要右移3位
因為shiftcls
前面還有3
個位域,為了避免影響它們赫模,故右移
將它們抹零
树肃。