Objc类结构分析

Objc类结构分析

objc_class & objc_object

objc_class是个结构体,所有的Class都是以它为模版创建的,以下是底层定义的一部分:

1
2
3
4
5
6
7
8
9
10
11
12
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags

// ....
}

objc_object的定义如下:

1
2
3
4
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};

可以看到objc_class这个结构体是继承自objc_object的,所以在OC里面所有对象、类和元类都有isa这个属性,这里要注意isasuperclass两个属性要区分开,我们可以看这个著名的走位图。

isa & superClass

类信息中的内容

struct在内存中所有成员变量都是放在一块的,根据内存对齐的规则依次排布,这就为我们提供了在lldb中探索类信息的手段,我们可以通过获取类的首地址加上偏移,即可访问各字段的值。

获取bits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED && !__LP64__
explicit_atomic<mask_t> _mask;
uint16_t _occupied;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED && __LP64__
explicit_atomic<mask_t> _mask;
uint16_t _occupied;
uint16_t _flags;
# define CACHE_T_HAS_FLAGS 1
#elif __LP64__
uint32_t _unused;
uint16_t _occupied;
uint16_t _flags;
# define CACHE_T_HAS_FLAGS 1
#else
uint16_t _occupied;
uint16_t _flags;
# define CACHE_T_HAS_FLAGS 1
#endif

};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};

// ...

}

按照上面的思路,通过偏移获取想要打印的字段,偏移的计算逻辑如下,isa占8字节,superclass占8字节,cache_t的大小则需要进一步分析。抛开static的成员变量,union为8位,_bucketsAndMaybeMask为无符号整形,因此也是8位,所以共16位。得到偏移后我们就可以观察bits中存储的信息了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 获取类的首地址
(lldb) p/x NSClassFromString(@"KHPerson")
(Class _Nullable) 0x00000001000081e8 KHPerson

// 首地址加上偏移 1000081e8 -> 100008208
(lldb) expr class_data_bits_t * $b = (class_data_bits_t *)0x0000000100008208

// 用class_data_bits_t结构提供的方法data获取bits
(lldb) p $b->data()
(class_rw_t *) 0x00006000002cc100

(lldb) p *(class_rw_t *)0x00006000002cc100
(class_rw_t) {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000232
}
}
firstSubclass = KHTeacher
nextSiblingClass = NSProcessInfo
}

拿到class_rw_t这个结构后,可以进一步探索属性表和方法表了。

属性列表、方法列表

class_rw_t结构里,有提供获取当前类属性和实例方法的函数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct class_rw_t {
// ...

const method_array_t methods() const {
auto alternates = methodAlternates();
if (auto *array = alternates.array)
return *array;
if (auto *list = alternates.list)
return method_array_t{list};
if (auto *relativeList = alternates.relativeList)
return method_array_t{relativeList};
return method_array_t{};
}

const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
auto &baseProperties = v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties;
return property_array_t{baseProperties};
}
}

const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
auto &baseProtocols = v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols;
return protocol_array_t{baseProtocols};
}
}

// ...
};

我们可以来验证一下属性是否真的存在property_array_t这个结构中,方法列表的验证也类似,这里就不重复了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 获取class_rw_t的方法与上面一样,这里就省略前面的步骤了
(lldb) expr class_rw_t *$b = $a->data()
(lldb) p *$b
(class_rw_t) {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000232
}
}
firstSubclass = KHTeacher
nextSiblingClass = NSProcessInfo
}

// 用properties方法拿到属性列表
(lldb) expr property_array_t $ppA = (*$b).properties()

// 用p/x打印出来可以看到里面只存了一个指针
(lldb) p/x $ppA
(property_array_t) {
list_array_tt<property_t, property_list_t, RawPtr> = {
storage = (_value = 0x0000000100008090)
}
}

// 把这个指针转成property_list_t,用这个结构去获取里面的元素
// 可以看到count=1,与测试类的定义一致,
(lldb) expr property_list_t *$ppl = (property_list_t *)0x0000000100008090
(lldb) p *$ppl
(property_list_t) {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 1)
}
(lldb) p (*$ppl).get(0)
(property_t) (name = "personName", attributes = "T@\"NSString\",C,N,V_personName")

成员变量

那么问题来了,成员变量存在哪里呢?在class_rw_t里面,有一个方法const class_ro_t *ro(),引入了一个新的结构class_ro_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif

union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};

explicit_atomic<const char *> name;
objc::PointerUnion<method_list_t, relative_list_list_t<method_list_t>, method_list_t::Ptrauth, method_list_t::Ptrauth> baseMethods;
objc::PointerUnion<protocol_list_t, relative_list_list_t<protocol_list_t>, PtrauthRaw, PtrauthRaw> baseProtocols;
const ivar_list_t * ivars;

const uint8_t * weakIvarLayout;

}

可以看到结构体中有一个ivars的变量,里面存放的就是成员变量,下面通过lldb进行一些验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 获取data段数据
expr class_rw_t *$rw = $bits->data()

// 获取ro字段中ivars列表
(lldb) expr class_ro_t *$ro = (class_ro_t *)(*$rw).ro()
(lldb) expr const ivar_list_t *$ivars = (*$ro).ivars

// 通过打印可以看到列表中有两个数据
(lldb) p/x *$ivars
(const ivar_list_t) {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 0x00000020, count = 0x00000002)
}

(lldb) p (*$ivars).get(0)
(ivar_t) {
offset = 0x00000001000081b8
name = 0x0000000100003f35 "hobby"
type = 0x0000000100003f80 "@\"NSString\""
alignment_raw = 3
size = 8
}

(lldb) p (*$ivars).get(1)
(ivar_t) {
offset = 0x00000001000081bc
name = 0x0000000100003f3b "_personName"
type = 0x0000000100003f80 "@\"NSString\""
alignment_raw = 3
size = 8
}

这里测试用的类KHPerson里面只定义了一个成员变量hobby和一个属性personName,而打印出来的ivars列表有两条数据,所以可以得出一个结论:

  • 通过{}定义的成员变量,会出现在成员变量表ivars中,属性定义的变量也会在这里面出现;
  • 通过@property定义的属性,会存在属性列表中,里面不包含成员变量。