通過上一篇文章對isa
的分析,我們知道了所有的對象都包含isa
,并且isa
存儲了類的相關(guān)信息,所以這篇文章我們主要通過isa
來引出類的底層結(jié)構(gòu)以及一些信息
代碼分析
創(chuàng)建對象
LYPerson *person = [[LYPerson alloc] init];
lldb分析
(lldb) x/4gx person
0x100513970: 0x001d80010000820d 0x0000000000000000
0x100513980: 0x0000000000000000 0x0000000000000000
(lldb)
通過之前的分析,我們知道所有對象的第一個屬性都是isa
,故上面的0x001d80010000820d
為isa
的地址,我們繼續(xù)通過打印isa
的地址來看下
(lldb) po 0x001d80010000820d & 0x00007ffffffffff8ULL
LYPerson
(lldb) p/x 0x001d80010000820d & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x0000000100008208
(lldb) po 0x0000000100008208
LYPerson
這里需要& 0x00007ffffffffff8ULL
主要是因為isa
只有中間的44(或33)
位才是類信息,所以需要把其它位置為0
,通過上面的地址打印我們得出結(jié)論:對象的isa
指向類信息,那么我們繼續(xù)來打印下類的isa
看看是什么
(lldb) x/4gx 0x0000000100008208
0x100008208: 0x00000001000081e0 0x00007fff98b0e118
0x100008218: 0x0000000100513990 0x0001802400000003
(lldb) p/x 0x00000001000081e0 & 0x00007ffffffffff8ULL
(unsigned long long) $5 = 0x00000001000081e0
(lldb) po 0x00000001000081e0
LYPerson
這里我們發(fā)現(xiàn)類的isa
只想的信息,打印出來也是LYPerson
,通過內(nèi)存地址我們可以肯定此處的LYPerson
跟上一步的LYPerson
不是同一個,但是類在內(nèi)存中又只會存在一份,因此我們引出了元類
的概念,至此,我們得出結(jié)論:類的isa
指向元類信息.
接下來我們給出類在內(nèi)存中只存在一份的證明以及元類
的說明
//MARK:--- 分析類對象內(nèi)存 存在個數(shù)
void testClassNum(){
Class class1 = [LYPerson class];
Class class2 = [LYPerson alloc].class;
Class class3 = object_getClass([LYPerson alloc]);
NSLog(@"\n%p-\n%p-\n%p-\n%p", class1, class2, class3);
}
元類是系統(tǒng)給的,其定義和創(chuàng)建都是由編譯器完成,在這個過程中,類的歸屬來自于元類(類似于對象的歸屬是類)
元類是類對象的類,每個類都有一個獨一無二的元類用來存儲類方法的相關(guān)信息罐孝。
元類本身是沒有名稱的,由于與類相關(guān)聯(lián),所以使用了同類名一樣的名稱
接下來我們繼續(xù)分析下元類的isa
(lldb) x/4gx 0x00000001000081e0
0x1000081e0: 0x00007fff98b0e0f0 0x00007fff98b0e0f0
0x1000081f0: 0x00000001006471f0 0x0003e03500000007
(lldb) p/x 0x00007fff98b0e0f0 & 0x00007ffffffffff8ULL
(unsigned long long) $7 = 0x00007fff98b0e0f0
(lldb) po 0x00007fff98b0e0f0
NSObject
通過打印isa
我們發(fā)現(xiàn)LYPerson
的元類的isa
指向了NSObject
,那么此處的NSObject
是不是就是我們的根類呢?我們來證明下
(lldb) p/x NSObject.class
(Class) $9 = 0x00007fff98b0e118 NSObject
很明顯,兩個NSObject
并不是同一個類,因此我們認定上面的NSObject
為根元類
,這里我們得出結(jié)論:元類的isa
指向根元類.接下來我們繼續(xù)打印根元類的isa
(lldb) x/4gx 0x00007fff98b0e0f0
0x7fff98b0e0f0: 0x00007fff98b0e0f0 0x00007fff98b0e118
0x7fff98b0e100: 0x00000001020081b0 0x0005e03100000007
(lldb) p/x 0x00007fff98b0e0f0 & 0x00007ffffffffff8ULL
(unsigned long long) $11 = 0x00007fff98b0e0f0
(lldb) po 0x00007fff98b0e0f0
NSObject
這里我們發(fā)現(xiàn)根元類的isa
與它本身的地址相同,由此我們得出結(jié)論:根元類的isa
指向它本身
isa & 繼承鏈
通過上面的分析再加上我們的繼承關(guān)系,于是就有了下面這個著名的圖
上面這幅圖,對于isa
的走位我們已經(jīng)通過代碼證明過了,沒有任何問題.那么關(guān)于集成鏈有兩點需要注意下
- 根元類的父類為根類
- 根類的父類為
nil
以上兩點也說明了NSObject
做為基類,是萬物起源,我們可以看下底層編譯代碼
struct NSObject_IMPL {
Class isa;
};
// 結(jié)構(gòu)體
typedef struct objc_class *Class;
Class分析
源碼分析
通過上面的分析我們來到了我們非常熟悉的Class
,接下來我們就來具體分析下Class
的定義objc_class
的源碼實現(xiàn)
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
//....方法部分省略嚣崭,未貼出
}
通過源碼我們可以發(fā)現(xiàn)objc_class
中包含的元素主要有ISA
,superclass
,cache
,bits
,對與ISA
我們已經(jīng)了解了,superclass
很明顯是父類,也沒什么好分析的,那么剩下的cache
和bits
,cache
主要是緩存數(shù)據(jù),我們可以放到后面,這里主要還是分析下bits
里面有哪些內(nèi)容,那么如何獲取到bits
呢,這里我們補充下個知識點--內(nèi)存偏移
內(nèi)存偏移
偏移地址是指段內(nèi)相對于段起始地址的偏移值,
例如一個存儲器的大小是1KB,可以把它分為4段蕉陋,第一段的地址范圍就是0—255阎姥,第二段的地址范圍就是256-511记舆,依次類推。
我們拿數(shù)組來舉例
//數(shù)組指針
int c[4] = {1, 2, 3, 4};
int *d = c;
NSLog(@"%p -- %p - %p", &c, &c[0], &c[1]);
NSLog(@"%p -- %p - %p", d, d+1, d+2);
0x7ffeefbff560 -- 0x7ffeefbff560 - 0x7ffeefbff564
0x7ffeefbff560 -- 0x7ffeefbff564 - 0x7ffeefbff568
通過上面我們可以得出:數(shù)組的首地址即位第一個元素的地址,第二個元素的地址即為第一個元素的地址加上元素類型所占的字節(jié)大小,int
占用4個字節(jié),即+4
,若為double
則+8
綜上,內(nèi)存偏移就可以理解為首地址加偏移量
class_data_bits_t bits分析
在了解了內(nèi)存偏移后,我們來嘗試下獲取bits
,首先ISA
和superclass
都是Class
類型,Class
是結(jié)構(gòu)體指針類型,占用8
個字節(jié),所以加一起是16
個字節(jié),接下來我們分析下cache
,源碼如下
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 是一個結(jié)構(gòu)體指針類型呼巴,占8字節(jié)
explicit_atomic<mask_t> _mask; //是mask_t 類型泽腮,而 mask_t 是 unsigned int 的別名,占4字節(jié)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; //是指針伊磺,占8字節(jié)
mask_t _mask_unused; //是mask_t 類型盛正,而 mask_t 是 uint32_t 類型定義的別名,占4字節(jié)
#if __LP64__
uint16_t _flags; //是uint16_t類型屑埋,uint16_t是 unsigned short 的別名豪筝,占 2個字節(jié)
#endif
uint16_t _occupied; //是uint16_t類型,uint16_t是 unsigned short 的別名摘能,占 2個字節(jié)
通過源碼,我們得出cache
占用16
個字節(jié),因此bits
距首地址偏移32
個字節(jié),接下來我們通過lldb
進行調(diào)試
// 打印LYPerson的內(nèi)存地址
(lldb) x/4gx LYPerson.class
0x100008200: 0x00000001000081d8 0x000000010034c140
0x100008210: 0x000000010184dd90 0x0001802400000003
// 首地址為0x100008200,偏移32位獲取class_data_bits_t
(lldb) p (class_data_bits_t *)0x100008220
(class_data_bits_t *) $30 = 0x0000000100008220
(lldb)
通過objc_class
源碼我們可以發(fā)現(xiàn)bits
中存儲的信息為class_rw_t
結(jié)構(gòu)體類型,可以通過data()
方法獲取.源碼如下
struct objc_class : objc_object {
...
class_rw_t *data() const {
return bits.data();
}
...
}
我們繼續(xù)通過lldb
調(diào)試
(lldb) p $30->data()
(class_rw_t *) $31 = 0x0000000100645c60
(lldb) p *$31
(class_rw_t) $32 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000216
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
接下來通過查看class_rw_t
的源碼,我們發(fā)現(xiàn),class_rw_t
存儲了屬性列表
,方法列表
,協(xié)議
,ro(class_ro_t類型)
等信息,如下圖
接下來,繼續(xù)通過lldb
調(diào)試
// 獲取屬性列表
(lldb) p $32.properties()
(const property_array_t) $33 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000081a8
arrayAndFlag = 4295000488
}
}
}
// 獲取屬性數(shù)組
(lldb) p $33.list
(property_list_t *const) $34 = 0x00000001000081a8
// 打印具體值
(lldb) p *$34
(property_list_t) $35 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
}
}
(lldb) p $32.methods()
(const method_array_t) $36 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000080e0
arrayAndFlag = 4295000288
}
}
}
(lldb) p $36.list
(method_list_t *const) $37 = 0x00000001000080e0
(lldb) p *$37
(method_list_t) $38 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 5
first = {
name = "sayName"
types = 0x0000000100003f78 "v16@0:8"
imp = 0x0000000100003d60 (KCObjc`-[LYPerson sayName])
}
}
}
(lldb) p $38.get(0)
(method_t) $39 = {
name = "sayName"
types = 0x0000000100003f78 "v16@0:8"
imp = 0x0000000100003d60 (KCObjc`-[LYPerson sayName])
}
(lldb) p $38.get(1)
(method_t) $40 = {
name = "saySex"
types = 0x0000000100003f78 "v16@0:8"
imp = 0x0000000100003d90 (KCObjc`-[LYPerson saySex])
}
(lldb) p $38.get(2)
(method_t) $41 = {
name = ".cxx_destruct"
types = 0x0000000100003f78 "v16@0:8"
imp = 0x0000000100003e10 (KCObjc`-[LYPerson .cxx_destruct])
}
(lldb) p $38.get(3)
(method_t) $42 = {
name = "name"
types = 0x0000000100003f8c "@16@0:8"
imp = 0x0000000100003dc0 (KCObjc`-[LYPerson name])
}
(lldb) p $38.get(4)
(method_t) $43 = {
name = "setName:"
types = 0x0000000100003f94 "v24@0:8@16"
imp = 0x0000000100003de0 (KCObjc`-[LYPerson setName:])
}
(lldb) p $38.get(5)
Assertion failed: (i < count), function get, file /Users/LY/Desktop/LY/objc4_debug/objc4-781/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb)
通過上面獲取到的properties
我們得到了類的所有屬性列表,但是卻并沒有找到對應(yīng)的成員變量的列表,于是我們通過查看上面的ro
的源碼可以發(fā)現(xiàn)有一個ivars
屬性,同樣的方式分析ro
我們找到了所有的成員變量,這里不做贅述.大家可以自行嘗試
通過上面獲取到的methods
我們得到了類的所有實例方法列表,但是卻并沒有找到對應(yīng)的類方法的列表,根據(jù)我們最開始探索的isa
指向,我們知道對象的isa
指向類,類的isa
指向元類.那么現(xiàn)在對象方法存在了類中,會不會類方法存在元類中呢,于是我們通過上面的步驟對元類進行調(diào)試,發(fā)現(xiàn)類方法確實存在元類中,這里不做贅述.大家可以自行嘗試
通過上面的調(diào)試我們得出如下結(jié)論
- 類中存儲了類的所有
屬性列表
,``方法列表,
協(xié)議等信息在
class_rw_t`中 - 類的方法列表中除了實例方法還包括
setter
和getter
方法 - 類方法并不存在類信息中而是存在元類中
- 成員變量存在
class_ro_t
的ivars
中
至此,對于類的結(jié)構(gòu)以及類中的class_data_bits_t
存儲了哪些信息我們已經(jīng)基本都了解了,這篇文章就先到這里了,后續(xù)我們再對類的cache
進行分析.