objc_class 靜態(tài)結(jié)構(gòu)分析
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;
class_data_bits_t bits;
}
今天看看 objc_class 中到底有哪些東西,去掉了結(jié)構(gòu)體函數(shù)也就幾個(gè)成員
-
isa從objc_object中繼承而來 -
superclass存放的是父類的指針 -
cache存放著一些使用過的方法緩存 -
bits存的是class_rw_t的指針,還有rr/alloc的一個(gè)標(biāo)記,和isa有點(diǎn)類似
類數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)
//在 class_data_bits_t 中主要的就是 class_rw_t
struct class_rw_t {
//這注釋什么意思, 難道是說這些都會(huì)體現(xiàn)在符號(hào)表里? 這個(gè)之后再探索
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
#if SUPPORT_INDEXED_ISA
uint32_t index;
#endif
}
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
猜測(cè):
- 這兩個(gè)結(jié)構(gòu)記錄著
屬性、方法、成員變量 -
class_rw_t應(yīng)該是readwrite,用來存儲(chǔ)擴(kuò)展協(xié)議、方法、成員變量,并含有只讀的class_ro_t ro -
class_ro_t應(yīng)該是readonly,經(jīng)過一旦編譯就不會(huì)改變
跑起來驗(yàn)證我的猜想
//定義的類結(jié)構(gòu)
@interface LGPerson : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
+ (void)sayHappy;
@end
//LGPerson *person = [LGPerson new];
- 準(zhǔn)備工作 找到類對(duì)象
//查看person對(duì)象內(nèi)存
(lldb) x/2gx person
0x101e07f90: 0x001d8001000023b5 0x0000000000000000
//拿到 isa,換算成類對(duì)象地址 # define ISA_MASK 0x00007ffffffffff8ULL
(lldb) p/x 0x001d8001000023b5 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x00000001000023b0
(lldb) po 0x00000001000023b0
LGPerson
- 類對(duì)象內(nèi)存數(shù)據(jù)
//LGPerson 0x00000001000023b0
//查看 LGPerson類對(duì)象內(nèi)存數(shù)據(jù)
(lldb) x/2gx 0x00000001000023b0
0x1000023b0: 0x001d800100002389 0x0000000100b37140
- 確實(shí)數(shù)據(jù)具體的位置
objc_class 各中數(shù)據(jù)的大小
struct objc_class : objc_object {
Class isa; // size 8 ,pointer
Class superclass; // size 8 ,pointer
cache_t cache; // size 16 , struct
class_data_bits_t bits; // size 8 , struct
}
typedef uint32_t mask_t; // x86_64 & arm64
struct cache_t {
struct bucket_t *_buckets; // size 8
mask_t _mask; // size 4
mask_t _occupied; // size 4
}
struct class_data_bits_t {
uintptr_t bits; // size 8
}
匯總一下
| 名稱 | isa | superclass | cache | bits |
|---|---|---|---|---|
| 類型 | Class | Class | cache_t | class_data_bits_t |
| 大小 | 8 | 8 | 16 | 8 |
其中 cache_t 和 bits 的是結(jié)構(gòu)體不是指針,struct大小根據(jù)成員的大小而變換。
那么偏移量的計(jì)算就很簡(jiǎn)單了
- 偏移到
bits查看
- 那么只要在類對(duì)象地址基礎(chǔ)上偏移
32就是bits的地址,驗(yàn)證一下
//LGPerson 0x00000001000023b0
//偏移32 查看bits 0x00000001000023b0 + 0d32 = 0x00000001000023d0
(lldb) x/2gx 0x00000001000023d0
0x1000023d0: 0x0000000101e062f4 0x0000000000000000
//0x00000001020012f4 就是bits的地址
//想拿到 class_rw_t 需要經(jīng)過MASK
// #define FAST_DATA_MASK 0x00007ffffffffff8UL
(lldb) p/x 0x0000000101e062f4 & 0x00007ffffffffff8UL
(unsigned long) $6 = 0x0000000101e062f0
//將計(jì)算你結(jié)果 強(qiáng)轉(zhuǎn) class_rw_t *
(lldb) p (class_rw_t *)0x0000000101e062f0
(class_rw_t *) $7 = 0x0000000101e062f0
當(dāng)然也可以用 objc_object 中的函數(shù) data() 拿到 class_rw_t *,效果是一樣的
(lldb) p (class_data_bits_t *)0x1000023d0
(class_data_bits_t *) $5 = 0x00000001000023d0
// 調(diào)用class_data_bits_t 中的data()函數(shù)
(lldb) p $5->data()
(class_rw_t *) $10 = 0x0000000101e062f0
- 查看 class_rw_t 中的數(shù)據(jù)
(class_rw_t *) $10 = 0x0000000101e062f0
(lldb) p *$5
(class_rw_t) $12 = {
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 = 0x0000000100001f80 "LGPerson"
}
- class_rw_t.properties
//打印 class_rw_t 的 properties
(lldb) p $12.properties
(property_array_t) $13 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000022f0
arrayAndFlag = 4294976240
}
}
}
(lldb) p $13.list
(property_list_t *) $14 = 0x00000001000022f0
(lldb) p $14.first
(property_t) $15 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
Fix-it applied, fixed expression was:
$14->first
最終在 properties 中找到了 定義的屬性 nickName
- class_rw_t.methods
(lldb) p $12.methods
(method_array_t) $17 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002240
arrayAndFlag = 4294976064
}
}
}
(lldb) p $17.list
(method_list_t *) $18 = 0x0000000100002240
(lldb) p $18->get(0)
(method_t) $22 = {
name = "sayHello"
types = 0x0000000100001f8b "v16@0:8"
imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}
(lldb) p $18->get(1)
(method_t) $23 = {
name = ".cxx_destruct"
types = 0x0000000100001f8b "v16@0:8"
imp = 0x0000000100001c60 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}
(lldb) p $18->get(2)
(method_t) $24 = {
name = "nickName"
types = 0x0000000100001f93 "@16@0:8"
imp = 0x0000000100001bf0 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
}
(lldb) p $18->get(3)
(method_t) $25 = {
name = "setNickName:"
types = 0x0000000100001f9b "v24@0:8@16"
imp = 0x0000000100001c20 (LGTest`-[LGPerson setNickName:] at LGPerson.h:17)
}
最終在 methods 中找到了
- 自己定義的
sayHello方法 - 系統(tǒng)生成的c++ 析構(gòu)方法
nickName - 系統(tǒng)生成的get方法
.cxx_destruct - 系統(tǒng)生成的set方法
setNickName:
- 查看 class_ro_t 中的數(shù)據(jù)
(lldb) p *$12.ro
(const class_ro_t) $29 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100001f89 "\x02"
name = 0x0000000100001f80 "LGPerson"
baseMethodList = 0x0000000100002240
baseProtocols = 0x0000000000000000
ivars = 0x00000001000022a8
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000022f0
}
- 這里有個(gè)
const說明class_ro_t是一個(gè)常量。 - 這里存放這類名 等信息
- class_ro_t.ivars
(lldb) p *$29.ivars
(const ivar_list_t) $31 = {
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) p $31.get(0)
(ivar_t) $33 = {
offset = 0x0000000100002378
name = 0x0000000100001e64 "hobby"
type = 0x0000000100001fa6 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $31.get(1)
(ivar_t) $34 = {
offset = 0x0000000100002380
name = 0x0000000100001e6a "_nickName"
type = 0x0000000100001fa6 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb)
這里找到了兩個(gè)成員變量
- 自定義的成員變量
hobby - 系統(tǒng)生成的成員變量
_nickName
- class_ro_t.baseMethodList
(lldb) p *$29.baseMethodList
(method_list_t) $36 = {
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) p $36.get(0)
(method_t) $37 = {
name = "sayHello"
types = 0x0000000100001f8b "v16@0:8"
imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}
(lldb) p $36.get(1)
(method_t) $38 = {
name = ".cxx_destruct"
types = 0x0000000100001f8b "v16@0:8"
imp = 0x0000000100001c60 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}
(lldb) p $36.get(2)
(method_t) $39 = {
name = "nickName"
types = 0x0000000100001f93 "@16@0:8"
imp = 0x0000000100001bf0 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
}
(lldb) p $36.get(3)
(method_t) $40 = {
name = "setNickName:"
types = 0x0000000100001f9b "v24@0:8@16"
imp = 0x0000000100001c20 (LGTest`-[LGPerson setNickName:] at LGPerson.h:17)
}
這里居然和 class_rw_t中的一模一樣
class_rw_t 是可以在運(yùn)行時(shí)來拓展類的一些屬性、方法和協(xié)議等內(nèi)容。
class_ro_t 是在編譯時(shí)就已經(jīng)確定了的,存儲(chǔ)的是類的成員變量、屬性、方法和協(xié)議等內(nèi)容。
運(yùn)行時(shí),會(huì)將 ro 中的數(shù)據(jù) copy 到 rw 中。
- 類方法在哪里?
我在 LGPerson中定義了 + (void)sayHappy;類方法但是好像并沒有找到,不出意外的話應(yīng)該在元類對(duì)象中。
// 0x00000001000023b0 LGPerson類對(duì)象
(lldb) x/4gx 0x00000001000023b0
0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da260 0x0000000000000000
//拿到LGPerson 元類對(duì)象
(lldb) p/x 0x001d800100002389 & 0x00007ffffffffff8ULL
(unsigned long long) $41 = 0x0000000100002388
//打印元類內(nèi)存數(shù)據(jù)
(lldb) x/6gx 0x0000000100002388
0x100002388: 0x001d800100b370f1 0x0000000100b370f0
0x100002398: 0x0000000100f42780 0x0000000300000007
0x1000023a8: 0x0000000101e062b0 0x001d800100002389
//拿到 元類的 class_rw_t *
(lldb) p (class_rw_t *)0x1000023a8
(class_rw_t *) $43 = 0x00000001000023a8
(lldb) p *$43
(class_rw_t) $44
p $44.ro
(const class_ro_t *) $45 = 0x001d800100002389
(lldb) p *$45
//error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
//最后發(fā)現(xiàn) 元類的 ro 好像不讓訪問
換一種方式證明
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
// 輸出 0x1000021e0-0x1000021e0
這結(jié)果太尷尬了都能找到,看了一下 class_getClassMethod的源碼找到的問題所在
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
Class getMeta() {
//如果是元類就返回 this,不是元類就返回 isa
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
class_getClassMethod 找元類中找到了sayHappy的實(shí)例方法,所以可以得出類方法是保存在元類中。
class_ro_t 和 class_rw_t 的關(guān)系
class_ro_t由編譯階段確定的只讀數(shù)據(jù)
class_rw_t是在運(yùn)行時(shí)創(chuàng)建
在什么時(shí)候創(chuàng)建 rw
-
realizeClass中對(duì)rw進(jìn)行了第一次初始化 - 在方法慢速查找的時(shí)候,如果類對(duì)象未實(shí)現(xiàn)
isRealized就會(huì)觸發(fā)realizeClass
realizeClass 調(diào)用棧
>objc_alloc
> callAlloc
> (匯編)_objc_msgSend_uncached
> lookUpImpOrForward
> realizeClass
realizeClass 中的rw和ro
static Class realizeClass(Class cls)
{
......
//初次讀取cls->data() 指向的是 class_ro_t
//之后的讀取cls->data() 指向的是 class_rw_t
//兩種結(jié)構(gòu)體 class_ro_t 和 class_rw_t 第一個(gè)成員都是flags,強(qiáng)轉(zhuǎn)至少保證了flag的正常獲取
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) { //判斷是否是rw
//如果rw已經(jīng)分配了內(nèi)存,則rw指向cls->data(),然后將rw的ro指針指向之前最開始的ro
rw = cls->data();
ro = cls->data()->ro;
//修改 rw的flags
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
//如果rw并不存在,則為rw分配空間
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
//將rw->ro設(shè)置為原始的ro
rw->ro = ro;
//設(shè)置 rw的flags
rw->flags = RW_REALIZED|RW_REALIZING;
//修改cls->data(),也就是讓cls->data()指向rw
cls->setData(rw);
}
isMeta = ro->flags & RO_META;
......
}
總結(jié)
- 實(shí)例方法存放在類對(duì)象中 ,類方法存放在元類對(duì)象中
- 類的方法、協(xié)議、屬性都存放在類對(duì)象的class_rw_t中
- class_ro_t 存放著編譯時(shí)確定的方法、協(xié)議、屬性、成員變量
遺留的問題
- 為什么元類對(duì)象的 ro 不能訪問
方案一
在realizeClass的時(shí)候看到了元類處理ro和rw的過程,直接拿其中的ro
// 用來判斷是否是元類的Mask #define RO_META (1<<0)
(lldb) po ro->flags & (1<<0)
1
(lldb) p *ro
(const class_ro_t) $0 = {
flags = 389
instanceStart = 40
instanceSize = 40
reserved = 0
ivarLayout = 0x0000000000000000
name = 0x0000000100001f85 "LGPerson"
baseMethodList = 0x00000001000021d8
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000000000000
}
(lldb) p $0.baseMethodList
(method_list_t *const) $4 = 0x00000001000021d8
(lldb) p $4->get(0)
(method_t) $5 = {
name = "sayHappy"
types = 0x0000000100001f90 "v16@0:8"
imp = 0x0000000100001bc0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
}
方案二
好吧必須承認(rèn)的是我粗心了,元類偏移32的指針直接當(dāng)成class_rw_t *來處理,實(shí)際上是class_data_bits_t *
//LGPerson的元類 0x0000000100002390
(lldb) x/6gx 0x0000000100002390
0x100002390: 0x001d800100b370f1 0x0000000100b370f0
0x1000023a0: 0x000000010180a1a0 0x0000000400000007
0x1000023b0: 0x000000010192ee50 0x001d800100002391
(lldb) p (class_data_bits_t *)0x1000023b0
(class_data_bits_t *) $14 = 0x00000001000023b0
(lldb) p *$14
(class_data_bits_t) $15 = (bits = 4321373776)
(lldb) p $14->data()
(class_rw_t *) $17 = 0x000000010192ee50
(lldb) p $17->ro
(const class_ro_t *) $18 = 0x00000001000021f8
(lldb) p *$18
(const class_ro_t) $19 = {
flags = 389
instanceStart = 40
instanceSize = 40
reserved = 0
ivarLayout = 0x0000000000000000
name = 0x0000000100001f85 "LGPerson"
baseMethodList = 0x00000001000021d8
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000000000000
}
(lldb) p $19.baseMethodList
(method_list_t *const) $20 = 0x00000001000021d8
(lldb) p $20->get(0)
(method_t) $21 = {
name = "sayHappy"
types = 0x0000000100001f90 "v16@0:8"
imp = 0x0000000100001bc0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
}
//激動(dòng)啊 折騰了兩天,終于看到了美麗的sayHappy
收工