[TOC]
-
怎么读到cache_t属性
image
四个属性
类结构源码在源码中关于类
objc_class
的结构体代码, 总共有200多行, 而我们最关注的只有上述四个属性
-
// Class ISA;
: 8字节- isa属性是继承与父类的属性, 所以是在属性的第一个位置, 表示isa指针
- 指针占8个字节
-
Class superclass;
: 8字节- 除了isa属性之外, 父类指针就是第二个位置, 表示父类的isa指针
- 指针占8个字节
-
cache_t cache;
: 16字节struct cache_t { struct bucket_t *_buckets; // 8 mask_t _mask; // 4 mask_t _occupied; // 4
我们可以先看看cache_t的结构, 根据内存对齐的规则, 可以计算出这个结构体的大小为
16字节
- cache_t结构体
- 16字节
-
class_data_bits_t bits;
: 所有的数据存储位置, 重点研究对象
探索四个属性
1. 准备类
@interface LGPerson : NSObject
{
NSString *_hobby;
}
@property (nonatomic, copy) NSString *lgName;
@end
- 定义一个成员变量
_hobby
- 定义一个属性
lgName
- 我们知道编译器会自动生成一个
lgName
的getter和setter方法和一个带下划线的成员变量_lgName
2. 查看 class_data_bits_t bits
属性
根据上面对objc_class
的四个属性的分析, class_data_bits_t bits
是在第四个属性的位置, 但不能仅根据属性字段的第四个位置的地址来输出它, 需要根据地址偏移来完成.
首地址偏移32位, 可以找到 bits 属性的地址,
class_data_bits_t
p (class_data_bits_t *)(0x1000039a0+32)
你肯定会有一个疑问, 为什么这里要强转成 class_data_bits_t *
而不是class_data_bits_t
.
我们来看下面一行代码
class_rw_t *data() {
return bits.data();
}
data返回的是是一个指针类的数据, 我们可以猜测这里有一大坨的数据, 怎么读取这一坨数据, 自然就是我们的指针了, 可能有其他理论依据, 但这里作为一个探索分析就只能这么分析了。
【注意】在我们自己的iOS APP工程里面是无法分析的,原因是因为没有导入objc源码,所以无法找到 class_data_bits_t
类。
3. 查看 class_rw_t bits.data()
数据源
上面的截图中可以知道, 1` 来读取
data
p $1->data()
4. 分析 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
...
在这里看到了一个class_ro_t *ro
, 不知道是什么, 但是既然是作为指针出现在这里, 一定是有它的意义的, 后面再做分析.
除了ro, 还有比较熟悉的几个属性,
- method_array_t methods: 方法数组
- property_array_t properties: 属性数组
- protocol_array_t protocols: 协议数组
在上面的截图中 (class_rw_t *) $2 = 0x0000000101e68ef0
, $2表示的是, class_rw_t *的指针, 那么可以尝试输出这个指针的值
class_rw_t
p *$2
5. 同样的方式分析ro
ro找到 ro 的指针
p $3.ro
打印 ro 的信息
p *$4
6. 探索LGPerson中的数据
-
成员变量 ivar
找到 ivars 的指针
$4->ivars
打印 ivars 的信息
p *$6
在
ivar_list_t
中有个count = 2
, 而我们知道我们定义的成员变量个数刚好为2,这里只输出了第一个, 所以下面要输出全部的成员变量.p $7.get(1)
-
方法 method
同样的操作, 找到了编译器为我们自动生成的getter和setter方法
method -
属性 properties
同样的操作, 找到了lgName存在的地方
properties
总结
这篇文章记录的是怎么探索类的结构,我们常用的数据都是在哪里、怎么存储的,没有对加载时机做分析(也即什么时候加载到这些地方),后面会讲。
探索完类的结构之后,有一种豁然开朗的感觉,以前都是靠猜测来分析属性、方法等存在哪里,现在能从内存中取出来,相当于是打开了潘多拉魔盒。
网友评论