如何打印類信息
通過lldb指令打印類信息
- 通過
isa
指針地址 & ISA_MASK
*NSObject
提供的class API
-
runtime
底層調(diào)用API
通過isa指針地址 & ISA_MASK
通過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
,通過0x001d8001000021e5 & 0x00007ffffffffff8ULL
得到類的指針地址``0x00000001000021e0
NSObject提供的class API
通過lldb
命令輸入p/x LGPerson.class
,調(diào)用NSObject
提供的class API
,得出類的指針地址
runtime底層調(diào)用的API
通過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è)東西就是元類,它沒有非常清晰的概念。它不能向類一樣時(shí)刻查看。它是系統(tǒng)給予
的非常重要
的東西。
- 當(dāng)前
對(duì)象isa
歸屬于類 - 類是一個(gè)
對(duì)象
,它的歸屬是元類
- 所有類方法的存儲(chǔ)都存在
元類
里面
元類的創(chuàng)建和定義都是由編譯器自動(dòng)完成
NSObject的根元類
通過lldb命令不停爬,最終從元類來到NSObject。順序就是isa 對(duì)象-> 類(LGPerson)->元類(LGPerson)-> NSObject(根元類)
進(jìn)行驗(yàn)證NSObject
是內(nèi)存的NSObject
嗎?
通過lldb輸入p/x NSObject.class
得到結(jié)果0x00000001003f0140
可以看出和0x00000001003f00f0
是不同的。
由此我們考慮一個(gè)問題(面試題目)
請(qǐng)問類對(duì)象和類信息在內(nèi)存里面存在幾份?
其實(shí)類對(duì)象內(nèi)存
里面永遠(yuǎn)只存在一份
但是上圖可以看出有兩個(gè)NSObject
,這又是為什么呢?
其實(shí)NSObject
是根元類和LGPerson
一個(gè)原理isa 對(duì)象-> 類(LGPerson)->元類(LGPerson)-> NSObject(根元類)
那就我們來驗(yàn)證一下NSObject
到底有多少份
通過上面發(fā)現(xiàn)地址都一樣可以驗(yàn)證類對(duì)象內(nèi)存
永遠(yuǎn)只存在一份
其實(shí)0x00000001003f0140
打印出的NSObject
是來自于NSObject
的元類(根元類)
我們可以通過lldb命令繼續(xù)驗(yàn)證,原理就是類的對(duì)象指向元類
,如圖可以發(fā)現(xiàn)打印結(jié)果是一樣
就此就驗(yàn)證了上面的說法。
于此同時(shí),我們?cè)?code>NSObject根元類的基礎(chǔ)上繼續(xù)x/4gx
打印信息,并且&0x00007ffffffffff8ULL
,可以發(fā)現(xiàn)它還是指向自己
由此可以總結(jié)出一幅圖
- 1:實(shí)例對(duì)象的
isa
指向class(類),類的isa
指向元類
,元類的isa
指向根元類
- 3:
RootClass
是來自于NSObject
的實(shí)例對(duì)象和NSObject
的類,NSObject
的類指向NSObject
的根元類
,根元類
根元類還是指向自己
(少了一步元類的指向)。 - 2:在
特殊
情況下,如果是父類
,實(shí)例對(duì)象
指向當(dāng)前的類
,類
指向當(dāng)前的元類
,元類
指向根元類
,根元類
還是指向根元類
image
LGTercher 繼承于 LGPerson是繼承關(guān)系 即
繼承關(guān)系來自于類
但是實(shí)例對(duì)象tercher和實(shí)例對(duì)象person沒有繼承關(guān)系 即實(shí)例對(duì)象沒有繼承關(guān)系
NSObject
父類的繼承關(guān)系來自于nil
(找到NSObject的類型就結(jié)束了)
元類
也存在繼承
,根元類指向NSObject
,萬物皆是由NSObject
創(chuàng)建即萬物皆對(duì)象
上下層的對(duì)接
搜索objc_object
進(jìn)入objc_object源碼
來自于OBJC_ISA_AVAILABILITY
通過搜索struct objc_class {
進(jìn)入objc_class
源碼,OBJC2_UNAVAILABLE
舊的同時(shí)已經(jīng)被廢除
- 很多的類底層都來自于
objc_class
結(jié)構(gòu)體,因此NSObject
也是來自于objc_class
-
objc_object
是我們當(dāng)前的根對(duì)象
,是一個(gè)結(jié)構(gòu)體
不管是NSObject
還是對(duì)象都有一個(gè)isa
,isa
來自于objc_object
,同時(shí)objc_class
繼承于objc_object
所以也是有isa
。
面試問題
objc_object
和對(duì)象的關(guān)系?
所有的對(duì)象都是以objc_object
為模版繼承過來的
總結(jié):所有的對(duì)象+類+元類 都有isa
. 所有的對(duì)象來自于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)存地址,并且他們都是來自于LGPerson
創(chuàng)建的,但是他們指針地址不同
,其實(shí)指向它們的是第二個(gè)指針地址
即指針的指針
內(nèi)存偏移
有時(shí)我們?nèi)〔坏街档臅r(shí)候而是取到對(duì)象
,現(xiàn)在我們可可以拿到首值
通過偏移
的方式獲取值。
- 因?yàn)闂J沁B續(xù)的,相差
4
個(gè)字節(jié) - 通過
首值
進(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é)
通過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ǔ)上,通過
p $5.methods()
命令中的methods方法
是由class_rw_t
提供的,返回的實(shí)際類型是method_array_t
- 由于list類型是
method_list_t
,是一個(gè)指針,所以通過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)添加