如何打印類信息
通過(guò)lldb指令打印類信息
- 通過(guò)
isa
指針地址 & ISA_MASK
*NSObject
提供的class API
-
runtime
底層調(diào)用API
通過(guò)isa指針地址 & ISA_MASK
通過(guò)alloc->_objc_rootAlloc->callAlloc->_objc_rootAllocWithZone->_class_createInstanceFromZone->initInstanceIsa->initIsa->isa_t
找到位域位置(目前是處于macOS,所以使用的是x86_64)
- 移動(dòng)端 :
__arm64__
-
ISA_MASK
:0x0000000ffffffff8ULL
-
- mac OS
__x86_64__
-
ISA_MASK
:0x00007ffffffffff8ULL
-
輸入x/4gx person1
,打印對(duì)象的isa指針得到0x001d8001000021e5
,通過(guò)0x001d8001000021e5 & 0x00007ffffffffff8ULL
得到類的指針地址``0x00000001000021e0
NSObject提供的class API
通過(guò)lldb
命令輸入p/x LGPerson.class
,調(diào)用NSObject
提供的class API
凰浮,得出類的指針地址
runtime底層調(diào)用的API
通過(guò)lldb
命令輸入p/x object_getClass(person1)
,調(diào)用runtime
底層調(diào)用API
,得出類的指針地址
算法特殊性
如圖可以看出0x00000001000021e0
和0x00000001000021b8
蹂风,打印的值都是LGPerson
客冈,即使0x00000001000021b8 & 0x00007ffffffffff8ULL
得出的指針地址還是一樣的告嘲,所以打印的結(jié)果還是LGPerson
丑勤,其實(shí)這里就是算法的特殊性(三位抹零)华嘹,里面做了處理。
那么為什么0x00000001000021e0
和0x00000001000021b8
打印的結(jié)果都是LGPerson
呢法竞?
其實(shí)這個(gè)東西就是元類耙厚,它沒(méi)有非常清晰的概念。它不能向類一樣時(shí)刻查看岔霸。它是系統(tǒng)給予
的非常重要
的東西薛躬。
- 當(dāng)前
對(duì)象isa
歸屬于類 - 類是一個(gè)
對(duì)象
,它的歸屬是元類
- 所有類方法的存儲(chǔ)都存在
元類
里面
元類的創(chuàng)建和定義都是由編譯器自動(dòng)完成
NSObject的根元類
通過(guò)lldb命令不停爬秉剑,最終從元類來(lái)到NSObject泛豪。順序就是isa 對(duì)象-> 類(LGPerson)->元類(LGPerson)-> NSObject(根元類)
進(jìn)行驗(yàn)證NSObject
是內(nèi)存的NSObject
嗎?
通過(guò)lldb輸入p/x NSObject.class
得到結(jié)果0x00000001003f0140
可以看出和0x00000001003f00f0
是不同的侦鹏。
由此我們考慮一個(gè)問(wèn)題(面試題目)
請(qǐng)問(wèn)類對(duì)象和類信息在內(nèi)存里面存在幾份诡曙?
其實(shí)類對(duì)象內(nèi)存
里面永遠(yuǎn)只存在一份
但是上圖可以看出有兩個(gè)NSObject
,這又是為什么呢略水?
其實(shí)NSObject
是根元類和LGPerson
一個(gè)原理isa 對(duì)象-> 類(LGPerson)->元類(LGPerson)-> NSObject(根元類)
那就我們來(lái)驗(yàn)證一下NSObject
到底有多少份
通過(guò)上面發(fā)現(xiàn)地址都一樣可以驗(yàn)證類對(duì)象內(nèi)存
永遠(yuǎn)只存在一份
其實(shí)0x00000001003f0140
打印出的NSObject
是來(lái)自于NSObject
的元類(根元類)
我們可以通過(guò)lldb命令繼續(xù)驗(yàn)證价卤,原理就是類的對(duì)象指向元類
,如圖可以發(fā)現(xiàn)打印結(jié)果是一樣
就此就驗(yàn)證了上面的說(shuō)法。
于此同時(shí),我們?cè)?code>NSObject根元類的基礎(chǔ)上繼續(xù)x/4gx
打印信息蒲列,并且&0x00007ffffffffff8ULL
僻孝,可以發(fā)現(xiàn)它還是指向自己
由此可以總結(jié)出一幅圖
- 1:實(shí)例對(duì)象的
isa
指向class(類),類的isa
指向元類
酿傍,元類的isa
指向根元類
- 3:
RootClass
是來(lái)自于NSObject
的實(shí)例對(duì)象和NSObject
的類,NSObject
的類指向NSObject
的根元類
,根元類
根元類還是指向自己
(少了一步元類的指向)岁疼。 - 2:在
特殊
情況下,如果是父類
缆娃,實(shí)例對(duì)象
指向當(dāng)前的類
捷绒,類
指向當(dāng)前的元類
,元類
指向根元類
贯要,根元類
還是指向根元類
image
LGTercher 繼承于 LGPerson是繼承關(guān)系 即
繼承關(guān)系來(lái)自于類
但是實(shí)例對(duì)象tercher和實(shí)例對(duì)象person沒(méi)有繼承關(guān)系 即實(shí)例對(duì)象沒(méi)有繼承關(guān)系
NSObject
父類的繼承關(guān)系來(lái)自于nil
(找到NSObject的類型就結(jié)束了)
元類
也存在繼承
暖侨,根元類指向NSObject
,萬(wàn)物皆是由NSObject
創(chuàng)建即萬(wàn)物皆對(duì)象
上下層的對(duì)接
搜索objc_object
進(jìn)入objc_object源碼
來(lái)自于OBJC_ISA_AVAILABILITY
通過(guò)搜索struct objc_class {
進(jìn)入objc_class
源碼崇渗,OBJC2_UNAVAILABLE
舊的同時(shí)已經(jīng)被廢除
- 很多的類底層都來(lái)自于
objc_class
結(jié)構(gòu)體字逗,因此NSObject
也是來(lái)自于objc_class
-
objc_object
是我們當(dāng)前的根對(duì)象
,是一個(gè)結(jié)構(gòu)體
不管是NSObject
還是對(duì)象都有一個(gè)isa
宅广,isa
來(lái)自于objc_object
葫掉,同時(shí)objc_class
繼承于objc_object
所以也是有isa
。
面試問(wèn)題
objc_object
和對(duì)象的關(guān)系乘碑?
所有的對(duì)象都是以objc_object
為模版繼承過(guò)來(lái)的
總結(jié):所有的對(duì)象+類+元類 都有isa
. 所有的對(duì)象來(lái)自于NSObject
(OC), 真正到底層的話是objc_object
(C ,C++),struct objc_object *class
定義一個(gè)class類型(所有的class都是以objc_object為模版)
類的內(nèi)存分布
如圖可以看到//Class ISA
的位置挖息,這只是提示作用,而不是單純的屏蔽isa
兽肤,但是我們?cè)诖蛴≈锌梢园l(fā)現(xiàn)LGPerson
是有isa(0x00000001000021b8)
套腹,原因是objc_class
繼承于objc_object
,因此也擁有了父類的isa
资铡。
類結(jié)構(gòu)源碼
下圖可以看出10
同時(shí)賦值給了a
和b
电禀,其實(shí)就是一種copy
即值拷貝
,但是a
和b
的地址是不同的笤休。
觀察下圖尖飞,可以發(fā)現(xiàn)指針地址
和內(nèi)存地址
都不同,p1和p2都創(chuàng)建了新的內(nèi)存地址,并且他們都是來(lái)自于LGPerson
創(chuàng)建的政基,但是他們指針地址不同
贞铣,其實(shí)指向它們的是第二個(gè)指針地址
即指針的指針
內(nèi)存偏移
有時(shí)我們?nèi)〔坏街档臅r(shí)候而是取到對(duì)象
,現(xiàn)在我們可可以拿到首值
通過(guò)偏移
的方式獲取值沮明。
- 因?yàn)闂J沁B續(xù)的辕坝,相差
4
個(gè)字節(jié) - 通過(guò)
首值
進(jìn)行內(nèi)存偏移
取值
內(nèi)存偏移
image
探索類對(duì)象
前面我們已經(jīng)知道objc_class
繼承于objc_object
但是我們?cè)趺粗?code>superclass和cache
占用多少字節(jié)呢?達(dá)到我們平移到bits的位置荐健。
那么我們就需要知道isa+superclass+cache = bits
cache_t
是一個(gè)結(jié)構(gòu)體酱畅,結(jié)構(gòu)體的指針是8字節(jié),但是占用的大小是根據(jù)內(nèi)部的屬性進(jìn)行判斷江场。
點(diǎn)擊跳轉(zhuǎn)cache_t
源碼纺酸,靜態(tài)和函數(shù)不存在結(jié)構(gòu)體中,所以可以排除址否,那么需要計(jì)算只有四個(gè)位置
-
isa
是占用8
字節(jié) -
superclass
也是class
類型餐蔬,所以也是占用8
字節(jié) -
cache_t
占用16字節(jié)
通過(guò)lldb命令,根據(jù)首地址并且平移32字節(jié)
打印bits
信息
$1
因?yàn)槭侵羔橆愋驮谡牛哉{(diào)用data()方法
用含。同時(shí)這是781的最新改版,舊的版本有顯示methdos properties
等數(shù)據(jù)帮匾。
- 指針和對(duì)象使用
->
- 結(jié)構(gòu)體使用
.
符號(hào)
781源碼如何打印methdos properties
等信息
- 在bits信息的基礎(chǔ)上啄骇,通過(guò)
p $5.methods()
命令中的methods方法
是由class_rw_t
提供的,返回的實(shí)際類型是method_array_t
- 由于list類型是
method_list_t
瘟斜,是一個(gè)指針缸夹,所以通過(guò)p *$7
獲取內(nèi)存中的信息
,也證明了bits
中存儲(chǔ)了method_list
-
p $8.get(1)
和p $8.get(2)
打印的是屬性的set
和get
方法螺句,由系統(tǒng)自動(dòng)生成虽惭。p $8.get(2)oc封裝于c++底層會(huì)默認(rèn)添加