類的結(jié)構(gòu)和定義
- 首先跟蹤源碼忠聚,找到Class的的定義,發(fā)現(xiàn)其本質(zhì)為objc_class類型的指針,并且 objc_class繼承自objc_object缘眶,其中objc_class中有一個(gè)隱藏的isa指針,最后在objc_object中發(fā)現(xiàn)了isa的定義
這里要注意的是髓废,在new版本的源碼中磅崭,objc_class繼承自objc_object,在之前的舊版本中瓦哎,isa指針直接定義在objc_class中砸喻,其中OC中的NSObject在編譯到底層的時(shí)候都會(huì)轉(zhuǎn)變成相應(yīng)的結(jié)構(gòu)體objc_object
// 舊的版本,在OBJC2中已經(jīng)廢棄
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
// 新的版本蒋譬,也是現(xiàn)在源碼編譯調(diào)試使用的版本
typedef struct objc_class *Class;
// objc_class定義
struct objc_class : objc_object {
// Class ISA; // 8
Class superclass; // 8
cache_t cache; // 16 不是8 // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
// 下面都是方法
}
// objc_object定義
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
2 類的成員的存儲(chǔ)
回到objc_class割岛,其內(nèi)部定義了四個(gè)成員如下
- Class isa
- Class superclass
- cache_t cache
- class_data_bits_t bits
- 首先我們知道isa的指針是關(guān)聯(lián)對(duì)象和類,superClass指向繼承類犯助,那么類的成員能夠存儲(chǔ)的地方就只有cache和bits
- 先看一下cache的結(jié)構(gòu)體定義(不是一個(gè)結(jié)構(gòu)體指針癣漆,是一個(gè)結(jié)構(gòu)體),其中 mask_t為固定的4字節(jié)類型的值剂买,而bucket_t則是一個(gè)8字節(jié)的指針惠爽,都不能存放我們定義的屬性值癌蓖,所以可以排除cache,這里也看出 cache的內(nèi)存大小只有4+4+8=16字節(jié)(64位下)
struct cache_t {
struct bucket_t *_buckets; // 8
mask_t _mask; // 4
mask_t _occupied; // 4
}
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
MethodCacheIMP _imp;
cache_key_t _key;
#else
cache_key_t _key;
MethodCacheIMP _imp;
#endif
}
- 這里我們可以大膽猜測(cè)婚肆,bits中是否存放有我們定義的成員以及方法
2.1屬性的存儲(chǔ)探索
- 首先為LGPerson新建一個(gè)成員變量hobby以及屬性nickName租副,并且添加了示例方法和類方法,下面開始代碼斷點(diǎn)
@interface LGPerson : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
+ (void)sayHappy;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
Class pClass = object_getClass(person);
NSLog(@"%@ - %p",person,pClass);
}
return 0;
}
- x/4gx 打印pClass的內(nèi)容较性,但是除了第一以及第二的內(nèi)存用僧,是我們熟悉的isa以及superClass指針以外,第三塊地址的內(nèi)容我們完全不知曉赞咙,第四塊地址直接就不存在
- 按照Class結(jié)構(gòu)體的成員定義順序责循,以及內(nèi)存對(duì)齊原則,我們嘗試用指針偏移的方法攀操,來找到第四塊地址bits的所在院仿,并且看看bits存放的內(nèi)容到底為何
// 第一步
(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da280 0x0000000000000000
(lldb)
- 首地址為0x1000023b0,其中isa指針占用8字節(jié)速和,superClass占用8字節(jié)歹垫,cache占用了16字節(jié)(上面計(jì)算過),那么按照內(nèi)存對(duì)齊原則健芭,我們用首地址偏移32個(gè)字節(jié)县钥,就應(yīng)該能得出bits的內(nèi)容 ,偏移后得出0x1000023d0為bits的理論地址慈迈,但是打印結(jié)果很迷茫若贮,這里我們強(qiáng)轉(zhuǎn)一下,再次打印痒留,終于打印出class_data_bits_t的結(jié)構(gòu)體
// 第二步
(lldb) po 0x1000023d0
objc[1017]: mutex incorrectly locked
4294976464
(lldb) p (class_data_bits_t *)0x1000023d0
(class_data_bits_t *) $2 = 0x00000001000023d0
- class_rw_t為類中存儲(chǔ)屬性和方法的地方谴麦,看一下class_rw_t的實(shí)現(xiàn),返回的是bits.data()伸头,我們這里調(diào)用一下data方法之后得出一個(gè)class_rw_t類型的指針匾效,直接取值,結(jié)果如下
// 第三步
(lldb) p $2->data()
(class_rw_t *) $4 = 0x0000000100fd3150
(lldb) p *$4
(class_rw_t) $5 = {
flags = 2148139008
version = 0
ro = 0x0000000100002308
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002240
arrayAndFlag = 4294976064
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000022f0
arrayAndFlag = 4294976240
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
demangledName = 0x0000000000000000
}
- 使用p命令查看結(jié)果中properties的值恤磷,此時(shí)出現(xiàn)了新的類型property_array_t(在源碼的objc-runtime-new.h中有其定義)面哼,為一個(gè)二維數(shù)組,繼續(xù)探索其內(nèi)部list扫步,進(jìn)行 p $6.list魔策,此時(shí)出現(xiàn)property_list_t類型,繼承自entsize_list_tt河胎,在其內(nèi)部發(fā)現(xiàn)first方法闯袒,嘗試打印,最后找到了屬性nickName
(lldb) p $5.properties
(property_array_t) $6 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000022f0
arrayAndFlag = 4294976240
}
}
}
(lldb) p $6.list
(property_list_t *) $7 = 0x00000001000022f0
(lldb)
(lldb) p $7.first
(property_t) $8 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
Fix-it applied, fixed expression was:
$7->first
(lldb)
// entsize_list_tt and property_list_t
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
Element first;
}
2.2 成員變量的存儲(chǔ)
- 為了直觀的體現(xiàn),我們先重新編譯運(yùn)行一下項(xiàng)目政敢,斷點(diǎn)打上其徙,先直接跳到輸出class_rw_t
(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da280 0x0000000000000000
(lldb) p (class_data_bits_t *)0x1000023d0
(class_data_bits_t *) $1 = 0x00000001000023d0
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000102139a80
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148139008
version = 0
ro = 0x0000000100002308
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002240
arrayAndFlag = 4294976064
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000022f0
arrayAndFlag = 4294976240
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
demangledName = 0x0000000000000000
}
- 我們已經(jīng)知道properties存放的是類的屬性,結(jié)合class_rw_t里的方法名稱喷户,可以先嘗試探索一下ro部分
- 先p出ro的地址唾那,得出一個(gè)class_ro_t類型的結(jié)構(gòu)體指針,我們直接取值摩骨,拿到class_ro_t結(jié)構(gòu)體的內(nèi)容通贞,從中可以找到ivars成員朗若,根據(jù)名字可以猜測(cè)恼五,成員變量有可能在其中
(lldb) p $3.ro
(const class_ro_t *) $4 = 0x0000000100002308
(lldb) p *$4
(const class_ro_t) $5 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100001f89 "\x02"
name = 0x0000000100001f80 "LGPerson"
baseMethodList = 0x0000000100002240
baseProtocols = 0x0000000000000000
ivars = 0x00000001000022a8
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000022f0
}
(lldb)
- 繼續(xù)尋找ivar的值,打印得到ivar_list_t類型的指針哭懈,依舊取值灾馒,輸出內(nèi)容,發(fā)現(xiàn)了我們定義的成員變量hobby
(lldb) p $5.ivars
(const ivar_list_t *const) $7 = 0x00000001000022a8
(lldb) p *$7
(const ivar_list_t) $8 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x0000000100002378
name = 0x0000000100001e64 "hobby"
type = 0x0000000100001fa6 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
(lldb)
- 到這里遣总,成員變量存放的位置我們也已經(jīng)找到睬罗,但是我們只是定義了一個(gè)hobby屬性,但是count顯示個(gè)數(shù)為2旭斥,我們用get方法拿到剩余的一個(gè)值_nickName容达,這里也證明了屬性的定義會(huì)自動(dòng)生成對(duì)應(yīng)的成員變量
(lldb) p $8.get(1)
(ivar_t) $9 = {
offset = 0x0000000100002380
name = 0x0000000100001e6a "_nickName"
type = 0x0000000100001fa6 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb)
2.3 方法的存儲(chǔ)
依然按照上面尋找成員變量存儲(chǔ)位置之時(shí)運(yùn)行的代碼,從打印出$3垂券,也就是class_rw_t的結(jié)構(gòu)開始進(jìn)行方法的查找花盐,可以發(fā)現(xiàn),class_rw_t中的properties代表著屬性的存儲(chǔ)菇爪,ro代表著成員變量的存儲(chǔ)算芯,那么可以推斷,methods則應(yīng)該存放類的方法
先執(zhí)行 p $3.methods方法凳宙,獲得一個(gè)method_array_t類型的結(jié)構(gòu)體熙揍,打印出其中的list地址,并且取值得到一個(gè)entsize_list_tt氏涩,內(nèi)部的第一個(gè)元素存放著我們的sayHello方法
(lldb) p $3.methods
(method_array_t) $12 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002240
arrayAndFlag = 4294976064
}
}
}
(lldb) p $12.list
(method_list_t *) $13 = 0x0000000100002240
(lldb) p *$13
(method_list_t) $14 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
first = {
name = "sayHello"
types = 0x0000000100001f8b "v16@0:8"
imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}
}
}
(lldb)
- 但是可以發(fā)現(xiàn)届囚,其數(shù)組個(gè)數(shù)count=4,也就是除了我們定義的sayHello方法之外還有另外的三個(gè)方法是尖,輸出打印其他三個(gè)方法名稱依次為C++的析構(gòu)函數(shù)destruct方法意系,屬性nickName的getter和setter方法
(lldb) p $14.get(1)
(method_t) $15 = {
name = ".cxx_destruct"
types = 0x0000000100001f8b "v16@0:8"
imp = 0x0000000100001c60 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}
(lldb) p $14.get(2)
(method_t) $16 = {
name = "nickName"
types = 0x0000000100001f93 "@16@0:8"
imp = 0x0000000100001bf0 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
}
(lldb) p $14.get(3)
(method_t) $17 = {
name = "setNickName:"
types = 0x0000000100001f9b "v24@0:8@16"
imp = 0x0000000100001c20 (LGTest`-[LGPerson setNickName:] at LGPerson.h:17)
}
- 至此,我們已經(jīng)探索出了類的示例方法存放在類的class_rw_t結(jié)構(gòu)體中的methods里面
2.4 類方法的存儲(chǔ)
通過上面的步驟析砸,我們已經(jīng)可以了解到類的實(shí)例方法的存儲(chǔ)昔字,但是并沒有發(fā)現(xiàn)類方法sayHello的存儲(chǔ),通過class_rw_t結(jié)構(gòu)體內(nèi)部的名稱分析,基本可以判斷并沒有適合存放類方法的位置
那么再回到Class的基本結(jié)構(gòu)成員作郭,isa陨囊,superClass,cache夹攒,bits四個(gè)成員蜘醋,其中bits.data就是我們一直在尋找的class_rw_t結(jié)構(gòu)體,已經(jīng)證明其內(nèi)部不可能存放類方法咏尝,cache內(nèi)部的8字節(jié)的bucket_t指針存放的是key和imp的鍵值對(duì)压语,和我們了解的方法的存儲(chǔ)結(jié)構(gòu)并不一樣,所以我們暫時(shí)先跳過编检。而superClass則是其父類胎食,LGPerson的父類為NSObject,但是NSObject內(nèi)部并沒有sayHello方法允懂,所以也可以排除在外
最后剩下isa指針厕怜,在之前的文章中isa指針走向
,我們探索過了isa指針的走向蕾总,了解到了類的isa指針粥航,指向的是一個(gè)同名類,我們把它叫做元類生百,那么類方法會(huì)不會(huì)保存在元類中递雀,我們測(cè)試一下lldb控制臺(tái)輸入命令 x/4gx pClass之后,先通過isa指針查找到LGPerson的元類
(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da280 0x0000000000000000
(lldb) p/x 0x001d800100002389&0x00007ffffffffff8ULL
(unsigned long long) $19 = 0x0000000100002388
(lldb) po 0x0000000100002388
LGPerson
(lldb)
- 因?yàn)樵愐彩穷惖囊环N蚀浆,也是繼承自NSObject的一種特殊結(jié)構(gòu)缀程,所以我們也可以依舊按照對(duì)類的查找方法來進(jìn)行元類的結(jié)構(gòu)探索,其中元類的地址為0x0000000100002388蜡坊,依次找出class_data_bits_t杠输,通過->data()方法找到class_rw_t結(jié)構(gòu)體,打印出里面的methods秕衙,獲取其中的list數(shù)組蠢甲,最后找到了我們定義的類方法sayHappy流程和查找類的實(shí)例方法一樣,所以直接看結(jié)果
(lldb) p $26.list
(method_list_t *) $27 = 0x00000001000021d8
(lldb) p *$27
(method_list_t) $28 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "sayHappy"
types = 0x0000000100001f8b "v16@0:8"
imp = 0x0000000100001bc0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
}
}
}
(lldb)
總結(jié)
- 類之間除了基本的繼承關(guān)系之外据忘,還依靠isa指針進(jìn)行對(duì)象和類的關(guān)聯(lián)鹦牛,也就是*對(duì)象-類-元類-根源類-根源類這一組isa關(guān)系圖
- 其中類的屬性和成員變量都存放在類的class_rw_t結(jié)構(gòu)體中
- 屬性的定義,還伴隨著成員變量以及其getter和setter的自動(dòng)生成
- 類的類方法勇吊,則以實(shí)例方法的形式曼追,存放在元類中,而元類又是繼承自NSObject汉规,形成一個(gè)閉環(huán)
- 至此礼殊,類的基本結(jié)構(gòu)以及其成員變量驹吮,屬性和方法的存儲(chǔ)也基本探索清楚了,如果有失誤或者補(bǔ)足的地方晶伦,還望留言一起討論~