前言
通過(guò)分析 alloc原理 和 內(nèi)存對(duì)齊原理缕探,只是了解了如何創(chuàng)建 對(duì)象
魂莫,alloc流程
及 內(nèi)存對(duì)齊
原則,卻對(duì) 對(duì)象
的本質(zhì)及 isa
知之甚少爹耗。下面具體理解一下對(duì)象的本質(zhì)及isa的原理耙考。
基本知識(shí)
位域
產(chǎn)生:有些信息在存儲(chǔ)時(shí),并不需要占用一個(gè)
完整的字節(jié)
潭兽,而只需占一個(gè)
或幾個(gè)
二進(jìn)制位倦始。例如在存放一個(gè)只有0
和1
兩種狀態(tài)時(shí), 用一個(gè)
二進(jìn)位即可山卦⌒兀基于這種原理,C語(yǔ)言提供了一種叫做位域
的數(shù)據(jù)結(jié)構(gòu)账蓉。定義:在定義結(jié)構(gòu)體或聯(lián)合體時(shí)枚碗,成員變量后面加
: 數(shù)字
,用來(lái)限定成員變量占用的位數(shù)剔猿,這就是位域视译。目的:節(jié)省存儲(chǔ)空間,處理簡(jiǎn)便归敬。
下面通過(guò)代碼理解位域的原理酷含。
創(chuàng)建兩個(gè)結(jié)構(gòu)體,定義屬性如下汪茧,一個(gè)使用位域前結(jié)構(gòu)體椅亚,一個(gè)使用位域后結(jié)構(gòu)體。
打印其內(nèi)存結(jié)果舱污,如下:
總結(jié):
沒(méi)有位域
的結(jié)構(gòu)體s1
占用的內(nèi)存空間大小為4
字節(jié)呀舔;使用位域
的結(jié)構(gòu)的大小為1
字節(jié)。
$ p s3
可得s3
的值:s3 = (a = 255, b = 255, c = NO, d = 255)
$ x/1bt &s3
可得內(nèi)存中二進(jìn)制數(shù)據(jù)為0x00001011
扩灯。低4位(從低到高)分別對(duì)應(yīng)的是a媚赖、b、c珠插、d的值惧磺;共占4個(gè)二進(jìn)制位,再進(jìn)行結(jié)構(gòu)體的內(nèi)存對(duì)齊捻撑,總大小為最大成員變量的整數(shù)倍磨隘,為1
字節(jié)缤底;
聯(lián)合體(union)
聯(lián)合體的語(yǔ)法和結(jié)構(gòu)體非常類似,但是它們占用內(nèi)存的情況卻不同番捂。
聯(lián)合體(union)和結(jié)構(gòu)體(struct)的差異:
- 結(jié)構(gòu)體的成員之間是
共存
的:各個(gè)成員占用不同的內(nèi)存个唧,它們互相之間沒(méi)有影響。 - 聯(lián)合體的成員之間是
互斥
的:所有成員共用同一段內(nèi)存设预,修改一個(gè)成員的值徙歼,會(huì)影響其余所有成員。 - 結(jié)構(gòu)體占用的內(nèi)存:
大于等于
所有成員占用內(nèi)存的總和(需要內(nèi)存對(duì)齊) - 聯(lián)合體占用的內(nèi)存:
等于最大
的成員占用的內(nèi)存絮缅,同一時(shí)刻只能
保存一個(gè)成員的值
下面通過(guò)代碼理解聯(lián)合體(union)和結(jié)構(gòu)體(struct)的差異鲁沥。
創(chuàng)建一個(gè)聯(lián)合體
對(duì)聯(lián)合體進(jìn)行賦值,并打印其內(nèi)存情況:
1. 沒(méi)有賦值的情況:
2. a
賦值的情況:
3. b
賦值的情況:
4. c
賦值的情況:
總結(jié):
聯(lián)合體可以定義多個(gè)不同類型的成員耕魄,聯(lián)合體的內(nèi)存大小由其中
最大的成員的大小
決定。聯(lián)合體中
修改
其中的某個(gè)變量會(huì)覆蓋
其他變量的值彭谁。聯(lián)合體所有的變量
公用
一塊內(nèi)存吸奴,變量之間互斥
。優(yōu)缺點(diǎn):
優(yōu)點(diǎn):節(jié)省內(nèi)存缠局。
缺點(diǎn):不夠包容则奥,同一時(shí)刻
只能
保存一個(gè)成員的值。
對(duì)象的本質(zhì)
如何對(duì)對(duì)象底層進(jìn)行探究狭园?首先了解一波 編譯器
準(zhǔn)備工作
- 新建一個(gè)
Project
- 創(chuàng)建如下文件读处,并添加屬性
編譯器 Clang
-
Clang
是?個(gè)C語(yǔ)?、C++唱矛、Objective-C語(yǔ)?的輕量級(jí)編譯器
罚舱。 -
Clang
將?持其普通lambda
表達(dá)式、返回類型的簡(jiǎn)化處理以及更好的處理constexpr
關(guān)鍵字绎谦。 -
Clang
是?個(gè)由Apple
主導(dǎo)編寫管闷,基于LLVM
的C/C++/Objective-C編譯器。
把?標(biāo)?件編譯成c++?件
$ clang -rewrite-objc main.m -o main.cpp
如果遇到如下 UIKit 未找到的問(wèn)題
執(zhí)行如下指令
$ clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.4 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk
為本地sdk
路徑
-runtime=ios-14.4
:14.4為版本號(hào)窃肠,可在下面的路徑拿到包个。
結(jié)果如下:
編譯器 xcrun
xcode
安裝的時(shí)候順帶安裝了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 os-mainarm64.cpp
模擬器編譯如下:
結(jié)果如下:
真機(jī)編譯如下:
結(jié)果如下:
main.cpp文件分析
1. 分析對(duì)象屬性
從源碼分析可得:
NSObject
的底層實(shí)現(xiàn)都是objc_object
結(jié)構(gòu)體類型。
struct ZLObject_IMPL:
對(duì)象的底層是結(jié)構(gòu)體
纤怒。嵌套
NSObject_IMPL
結(jié)構(gòu)體糯而。即對(duì)象
繼承于NSObject
。屬性以
_
開(kāi)頭的肪跋,代表成員屬性歧蒋。
其中 NSObject_IVARS
是什么土砂?并不知道。
2. 分析 NSObject_IMPL
NSObject_IMPL
里面只有一個(gè)成員變量是Class isa
谜洽。
3. 分析 Class
在 main.cpp
文件中全局搜索 *Class
萝映。代碼如下:
從源碼分析可得:
Class
的底層是objc_class
類型的結(jié)構(gòu)體指針。
objc_object
里面也是只有一個(gè)成員變量是Class isa
阐虚。泛型指針
id
:常用的id
是一個(gè)objc_object
結(jié)構(gòu)體指針序臂,這就為什么平時(shí)在使用id
修飾變量時(shí)為什么不加*
。
SEL
:SEL
也是結(jié)構(gòu)體指針实束。
4. 分析 get
和 set
方法
從源碼分析可得:
屬性的
get
和set
方法中通過(guò)獲取當(dāng)前對(duì)象的首地址
+變量的偏移值
來(lái)得到當(dāng)前變量奥秆,進(jìn)而實(shí)現(xiàn)get
和set
對(duì)當(dāng)前變量的修改和獲取。定義的屬性咸灿,底層自動(dòng)添加了
get
和set
方法构订。get
方法名為_I_類名_屬性名
,set
方法名為_I_類名_set屬性名_
避矢,例如:_I_ZLObject_name
和_I_ZLObject_setName_
悼瘾。隱含參數(shù):當(dāng)前對(duì)象
self
,方法_cmd
审胸。
isa的本質(zhì)
在分析 alloc原理 時(shí)亥宿,跳過(guò)了對(duì) isa
的分析,下面具體分析 isa
的原理砂沛。
回憶 alloc
流程烫扼,其流程圖如下:
其中 obj->initInstanceIsa
方法如下:
最終都會(huì)執(zhí)行 objc_object::initIsa
方法:
這也印證了對(duì)象的底層就是
objc_object
結(jié)構(gòu)體的觀點(diǎn),即對(duì)象
在調(diào)用initIsa
方法時(shí)碍庵,也是底層objc_object
的結(jié)構(gòu)體調(diào)用initIsa
方法映企。
initIsa 的流程分析
1. isa_t 分析
總結(jié):
isa_t
是一個(gè)聯(lián)合體,聯(lián)合體的成員變量存儲(chǔ)是互斥
的怎抛。有兩個(gè)成員變量
bits
和cls
卑吭,使用同一塊內(nèi)存。
2. ISA_BITFIELD 分析
其中 __has_feature(ptrauth_calls)
是什么呢马绝?
-
__has_feature
:此函數(shù)的功能是判斷編譯器是否支持某個(gè)功能 -
ptrauth_calls
:指針身份驗(yàn)證豆赏,針對(duì)arm64e
架構(gòu);使用Apple A12
或更高版本A
系列處理器的設(shè)備(如iPhone XS
富稻、iPhone XS Max
和iPhone XR
或更新的設(shè)備)支持arm64e
架構(gòu) - 參考鏈接:developer.apple.com
- 驗(yàn)證流程請(qǐng)參考 jr大神掷邦。
針對(duì)這三種類型,其 64
位存儲(chǔ)分布圖如下:
遍歷分析如下:
-
nonpointer
:是否對(duì)isa
指針開(kāi)啟指針優(yōu)化椭赋。0
純isa指針抚岗;1
不?是類對(duì)象地址,isa
中包含了類信息哪怔、對(duì)象的引?計(jì)數(shù)等宣蔚。 -
has_assoc
:關(guān)聯(lián)對(duì)象標(biāo)志位向抢,0
沒(méi)有,1
存在 -
has_cxx_dtor
:是否有 C++ 或者 Objc 的析構(gòu)器胚委,1
有析構(gòu)函數(shù)挟鸠,需要做析構(gòu)邏輯;
0
沒(méi)有亩冬,則可以更快的釋放對(duì)象艘希。 -
shiftcls
: 存儲(chǔ)類指針的值。 -
magic
:?于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象硅急,還是沒(méi)有初始化的空間覆享。 -
weakly_referenced
:是否被指向或者曾經(jīng)指向?個(gè) ARC 的弱變量,沒(méi)有弱引?的對(duì)象可以更快釋放营袜。 -
deallocating
:是否正在釋放內(nèi)存撒顿。 -
has_sidetable_rc
:當(dāng)對(duì)象引?技術(shù)?于 10 時(shí),則需要借?該變量存儲(chǔ)進(jìn)位荚板。 -
extra_rc
:對(duì)象的引?計(jì)數(shù)值核蘸,實(shí)際上是引?計(jì)數(shù)值減 1。例如啸驯,如果對(duì)象的引?計(jì)數(shù)為 10,那么extra_rc
為 9祟峦。如果引?計(jì)數(shù)?于 10罚斗,則需要使?has_sidetable_rc
存儲(chǔ)進(jìn)位。
3. 關(guān)聯(lián)類的方式
方式一:位運(yùn)算 (此處以 __86_64__
為例)
類信息是存儲(chǔ)在
isa
指針中宅楞,其中shiftcls
是對(duì)應(yīng)的對(duì)象信息针姿。按位偏移是目的是只保留shiftcls
信息,其它位的信息清0
厌衙。最終shiftcls
的相對(duì)位置要保持不變距淫。如圖:
方式二:與運(yùn)算 isa
& ISA_MASK
其中
ISA_MASK
為掩碼,用于與isa指針地址與運(yùn)算婶希。值也是區(qū)分內(nèi)核的:
__x86_64__
內(nèi)核下是0x00007ffffffffff8
arm64e
內(nèi)核和模擬器下是0x007ffffffffffff8
除
arm64e
以外的其他arm64
內(nèi)核下是0x0000000ffffffff8
方式三:原生方法 setClass
shiftcls = (uintptr_t)newCls >> 3
補(bǔ)充:為什么類的isa和元類的地址是一樣的榕暇?
原理: 從
isa
的結(jié)構(gòu)來(lái)看,對(duì)于對(duì)象來(lái)說(shuō)喻杈,沒(méi)有是否釋放
彤枢、引用計(jì)數(shù)
等字段,存儲(chǔ)的只有元類
本身筒饰,所以類的isa
和元類的地址是一樣缴啡。