前言
通常在創(chuàng)建對象的時候熊榛,都會繼承 NSObject
去新建一個類疯攒,那么NSObject
繼承誰会喝?或者說類的底層原理是什么末秃?下面來具體探究一下概页。
本文探索過程會涉及到 對象的本質(zhì)
準備工作
- 新建一個
Project
- 在
main.m
中添加一個類ZLObject
,打上斷點并執(zhí)行练慕。
案例分析
1. 探索對象的底層
$ p obj
:查看obj
對象的地址惰匙。
$ x/4gx obj地址
:查看obj
對象的isa及內(nèi)存占用。
$ p/x isa地址 & 掩碼地址
:與掩碼做與運算
$ po 與運算地址
:查看關(guān)聯(lián)類
流程如下:
其中掩碼為
__86_64__
的掩碼地址0x00007ffffffffff8ULL
铃将,最終得到ZLObject
的地址:0x0000000100008260
2. 繼續(xù)探索
以上面
ZLObject
的0x0000000100008260
地址项鬼,再次進行isa
和ISA_MASK
與運算,最終得到0x0000000100008238
的地址劲阎,還是ZLObject
绘盟。
這就比較奇怪了,為什么都是 ZLObject
悯仙,內(nèi)存地址卻不一樣龄毡?
3. 再次探索
再次以新的
ZLObject
的0x0000000100008238
地址,再次進行isa
和ISA_MASK
與運算锡垄,最終得到0x00007fff92c9d0f0
的地址沦零,是NSObject
。
對此货岭,有兩個疑問:
兩次的
ZLObject
是否存在的一定的聯(lián)系路操?
NSObject
的isa
指向了什么?
4. 方法印證
添加下圖方法茴她,并打印其內(nèi)存情況寻拂。
打印結(jié)果如下:
通過上面的案例,得到的結(jié)論是:對象的
isa
是指向類
丈牢,也就是ZLObject
的內(nèi)存地址0x0000000100008260
祭钉,那么類的isa
指向的是什么?為什么這塊內(nèi)存地址也是ZLObject
己沛。
5. MachO文件分析
有關(guān)MachO文件探索慌核,請移步 MachO文件分析
通過
MachOView
的分析距境,直接定義到Symbol Table
下查看所有的symbols
數(shù)據(jù),搜索class
垮卓,得出:
- 找到了
0x0000000100008260
的內(nèi)存地址垫桂,其符號下標(biāo)就是_OBJC_CLASS_$_ZLObject
,也就是ZLObject
粟按。- 找到了
0x0000000100008238
的內(nèi)存地址诬滩,其符號下標(biāo)就是_OBJC_METACLASS_$_ZLObject
,稱為ZLObject
的元類灭将。
結(jié)論一
對象的
isa
指向類
類的
isa
指向元類
元類
是系統(tǒng)編譯器生成的疼鸟。
MetaClass的本質(zhì)
上面的分析中,提出了兩個疑問庙曙,其中第一個已經(jīng)證實空镜,兩次的 ZLObject
,一個是 類
捌朴,一個是 元類
吴攒。那么,NSObject
的 isa
指向了什么砂蔽?接下來我們繼續(xù)探索洼怔。
通過查看 NSObject.class
的內(nèi)存地址 0x00007fff92c9d118
,發(fā)現(xiàn)和之前的 NSObject
地址 0x00007fff92c9d0f0
不一樣察皇,因此 0x00007fff92c9d0f0
為 NSObject
的 元類
茴厉。
那么 NSObject元類
的 isa
又指向了什么?
通過運算分析什荣,NSObject元類
的 isa
還是指向 NSObject元類
矾缓。
結(jié)論二
元類
的isa
指向根元類
根元類
的isa
還是指向根元類
對于
NSObject
,它也是根類
稻爬,根類
的isa
也是指向根元類
SuperClass的本質(zhì)
上面分析了 ZLObject
類和 NSObject
類的 isa
指向情況嗜闻,那么父類 SuperClass
的 isa
指向情況和繼承關(guān)系如何呢?
1. NSObject SuperClass
創(chuàng)建如圖類桅锄,并打印其內(nèi)存情況:
打印結(jié)果如下:
說明:
NSObject
的父類是nil
NSObject
的元類的父類還是NSObject
2. ZLObject SuperClass
首先看一下類的父類是 NSObject
的情況琉雳,打印其元類:
打印結(jié)果如下:
說明:
ZLObject
的元類的父類是NSObject
的元類
3. ZLSubObject SuperClass
創(chuàng)建繼承于 ZLObject
的子類 ZLSubObject
,并打印如圖內(nèi)存:
打印情況如下:
說明:
ZLSubObject
的元類的父類是ZLObject
的元類友瘤。
結(jié)論三
NSObject
的父類是nil
翠肘,其元類的父類還是NSObject
父類的元類也有繼承關(guān)系。
最終得到兩個關(guān)系圖辫秧,一個是類的 isa
指向圖束倍,一個是類的繼承鏈圖。
內(nèi)存偏移
1. 普通指針
打印結(jié)果:
說明:常量10處于
常量區(qū)
,可以被不同
的指針引用绪妹,其引用原理為值拷貝
甥桂。
2. 數(shù)組指針
打印結(jié)果:
說明:
使用數(shù)組
下標(biāo)
取地址,和利用指針偏移量
取值效果一樣邮旷。不如上圖中是&c[0]
和b + 1
數(shù)組的
首地址
也就是數(shù)組第一個
元素的地址黄选。指針
偏移量大小
和數(shù)組元素所占用字節(jié)大小
有關(guān),比如上圖中是int
婶肩,所以打印結(jié)果地址相差4
办陷,也稱步長
。
類的內(nèi)存結(jié)構(gòu)
分析源碼可知狡孔,objc_class
方法實現(xiàn)如下:
其內(nèi)存結(jié)構(gòu)圖如下:
因此如果想要得到 bits
懂诗,就必須知道 superclass
和 cache
的內(nèi)存字節(jié)數(shù)蜂嗽,再利用內(nèi)存偏移得到 bits
苗膝。
由于 isa
是 8
字節(jié),此處不再贅述植旧。
1. superclass
superclass
和 isa
一樣辱揭,也是 8
字節(jié),因為都是 Class
結(jié)構(gòu)體類型
2. cache
cache_t
的有效代碼如下:
cache_t
中的方法和static
聲明的字段都不是在該結(jié)構(gòu)體內(nèi)病附,所以只需要分析上面的有效代碼问窃,獲取cache_t
所占用的內(nèi)存大小,即可得到bits
的內(nèi)存偏移量完沪。
1. 聯(lián)合體之外的 explicit_atomic<uintptr_t>
的大杏虮印:
explicit_atomic
為泛型指針,所以其內(nèi)存大小由 <uintptr_t>
決定的覆积,也就是 uintptr_t
的大小听皿,為 8
字節(jié)。也可以使用 sizeof(uintptr_t)
查看其字節(jié)大小宽档。
2. 聯(lián)合體的大形疽獭:
說明:
mask_t
為uint32_t
類型,所以為4
吗冤,uint16_t
類型為2
又厉。
<preopt_cache_t *>
為結(jié)構(gòu)體指針,所以為8
椎瘟。聯(lián)合體內(nèi)部
內(nèi)存共用
覆致,且互斥
的特性,所以總大小為8
肺蔚。
結(jié)論
cache
所占字節(jié)為16
bits
的內(nèi)存偏移量為isa
+superclass
+cache
煌妈,總大小為32
類的底層數(shù)據(jù)獲取
1. 獲取 bits
數(shù)據(jù):
上圖可得類的首地址為:0x1000081e8
,那么以類的首地址偏移 32
字節(jié),就是 bits
声旺。
此時 bits
的數(shù)據(jù)存儲在 $4
中笔链。
在上面分析 objc_class
的結(jié)構(gòu)時,可以對 data()
進行存取數(shù)據(jù)腮猖。
因此鉴扫,可以通過 $4->data()
方法獲取 class_rw_t
內(nèi)存地址:
2. 獲取 class_rw_t
數(shù)據(jù):
這樣 class_rw_t
的數(shù)據(jù)就可以拿到了,但是并不知道所需的屬性和方法具體存在哪里澈缺。
3. 分析 class_rw_t
結(jié)構(gòu)體:
通過 properties()
和 methods()
獲取類的屬性和方法坪创。
4. 獲取 property_array_t
的 list
:
5. 獲取 list
的 ptr
:
6. 獲取 property_list_t
的數(shù)據(jù) :
7. 使用 C++數(shù)組 get()
方法,獲取類的屬性 :
8. 同理獲取類的 methods()
:
最后一步的 get(0)
沒有拿到數(shù)據(jù)姐赡,因此獲取方法和屬性不一樣莱预。