OC类的结构 objc_class
2020/3/10 23:01:42
本文主要是介绍OC类的结构 objc_class,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
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]; 复制代码
1. 准备工作 找到类对象
//查看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 复制代码
2. 类对象内存数据
//LGPerson 0x00000001000023b0 //查看 LGPerson类对象内存数据 (lldb) x/2gx 0x00000001000023b0 0x1000023b0: 0x001d800100002389 0x0000000100b37140 复制代码
3. 确定各个数据的偏移量
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
大小根据成员的大小而变换。
那么偏移量的计算就很简单了
4. 偏移到 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 复制代码
5. 查看 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" } 复制代码
6. 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
7. 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:
8. 查看 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
是一个常量。 - 这里存放这类名 等信息
9. 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
10. 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 中。
11. 类方法在哪里?
我在 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 复制代码
这篇关于OC类的结构 objc_class的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值