objc_class 静态结构分析
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;
class_data_bits_t bits;
}
今天看看 objc_class
中到底有哪些东西,去掉了结构体函数也就几个成员
-
isa
从objc_object
中继承而来 -
superclass
存放的是父类的指针 -
cache
存放着一些使用过的方法缓存 -
bits
存的是class_rw_t
的指针,还有rr/alloc
的一个标记,和isa
有点类似
类数据存储结构
//在 class_data_bits_t 中主要的就是 class_rw_t
struct class_rw_t {
//这注释什么意思, 难道是说这些都会体现在符号表里? 这个之后再探索
// 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;
}
};
猜测:
- 这两个结构记录着
属性、方法、成员变量
-
class_rw_t
应该是readwrite
,用来存储扩展协议、方法、成员变量
,并含有只读的class_ro_t ro
-
class_ro_t
应该是readonly
,经过一旦编译就不会改变
跑起来验证我的猜想
//定义的类结构
@interface LGPerson : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
+ (void)sayHappy;
@end
//LGPerson *person = [LGPerson new];
- 准备工作 找到类对象
//查看person对象内存
(lldb) x/2gx person
0x101e07f90: 0x001d8001000023b5 0x0000000000000000
//拿到 isa,换算成类对象地址 # define ISA_MASK 0x00007ffffffffff8ULL
(lldb) p/x 0x001d8001000023b5 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x00000001000023b0
(lldb) po 0x00000001000023b0
LGPerson
- 类对象内存数据
//LGPerson 0x00000001000023b0
//查看 LGPerson类对象内存数据
(lldb) x/2gx 0x00000001000023b0
0x1000023b0: 0x001d800100002389 0x0000000100b37140
- 确实数据具体的位置
objc_class 各中数据的大小
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
的是结构体不是指针,struct
大小根据成员的大小而变换。
那么偏移量的计算就很简单了
- 偏移到
bits
查看
- 那么只要在类对象地址基础上偏移
32
就是bits
的地址,验证一下
//LGPerson 0x00000001000023b0
//偏移32 查看bits 0x00000001000023b0 + 0d32 = 0x00000001000023d0
(lldb) x/2gx 0x00000001000023d0
0x1000023d0: 0x0000000101e062f4 0x0000000000000000
//0x00000001020012f4 就是bits的地址
//想拿到 class_rw_t 需要经过MASK
// #define FAST_DATA_MASK 0x00007ffffffffff8UL
(lldb) p/x 0x0000000101e062f4 & 0x00007ffffffffff8UL
(unsigned long) $6 = 0x0000000101e062f0
//将计算你结果 强转 class_rw_t *
(lldb) p (class_rw_t *)0x0000000101e062f0
(class_rw_t *) $7 = 0x0000000101e062f0
当然也可以用 objc_object
中的函数 data()
拿到 class_rw_t *
,效果是一样的
(lldb) p (class_data_bits_t *)0x1000023d0
(class_data_bits_t *) $5 = 0x00000001000023d0
// 调用class_data_bits_t 中的data()函数
(lldb) p $5->data()
(class_rw_t *) $10 = 0x0000000101e062f0
- 查看 class_rw_t 中的数据
(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
方法 - 系统生成的c++ 析构方法
nickName
- 系统生成的get方法
.cxx_destruct
- 系统生成的set方法
setNickName:
- 查看 class_ro_t 中的数据
(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
}
- 这里有个
const
说明class_ro_t
是一个常量。 - 这里存放这类名 等信息
- 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)
这里找到了两个成员变量
- 自定义的成员变量
hobby
- 系统生成的成员变量
_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 是可以在运行时来拓展类的一些属性、方法和协议等内容。
class_ro_t 是在编译时就已经确定了的,存储的是类的成员变量、属性、方法和协议等内容。
运行时,会将 ro 中的数据 copy 到 rw 中。
- 类方法在哪里?
我在 LGPerson
中定义了 + (void)sayHappy;
类方法但是好像并没有找到,不出意外的话应该在元类对象
中。
// 0x00000001000023b0 LGPerson类对象
(lldb) x/4gx 0x00000001000023b0
0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da260 0x0000000000000000
//拿到LGPerson 元类对象
(lldb) p/x 0x001d800100002389 & 0x00007ffffffffff8ULL
(unsigned long long) $41 = 0x0000000100002388
//打印元类内存数据
(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
//最后发现 元类的 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
这结果太尴尬了都能找到,看了一下 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
的实例方法,所以可以得出类方法是保存在元类中。
class_ro_t 和 class_rw_t 的关系
class_ro_t
由编译阶段确定的只读数据
class_rw_t
是在运行时创建
在什么时候创建 rw
-
realizeClass
中对rw进行了第一次初始化 - 在方法慢速查找的时候,如果类对象未实现
isRealized
就会触发realizeClass
realizeClass 调用栈
>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
//两种结构体 class_ro_t 和 class_rw_t 第一个成员都是flags,强转至少保证了flag的正常获取
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) { //判断是否是rw
//如果rw已经分配了内存,则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设置为原始的ro
rw->ro = ro;
//设置 rw的flags
rw->flags = RW_REALIZED|RW_REALIZING;
//修改cls->data(),也就是让cls->data()指向rw
cls->setData(rw);
}
isMeta = ro->flags & RO_META;
......
}
总结
- 实例方法存放在类对象中 ,类方法存放在元类对象中
- 类的方法、协议、属性都存放在类对象的class_rw_t中
- class_ro_t 存放着编译时确定的方法、协议、属性、成员变量
遗留的问题
- 为什么元类对象的 ro 不能访问
方案一
在realizeClass
的时候看到了元类处理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)
}
方案二
好吧必须承认的是我粗心了,元类偏移32的指针直接当成class_rw_t *
来处理,实际上是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)
}
//激动啊 折腾了两天,终于看到了美丽的sayHappy
收工
网友评论