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 superclass; cache_t cache; class_data_bits_t bits;
}
|
objc_object
的定义如下:
1 2 3 4
| struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; };
|
可以看到objc_class
这个结构体是继承自objc_object
的,所以在OC里面所有对象、类和元类都有isa
这个属性,这里要注意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
(lldb) expr class_data_bits_t * $b = (class_data_bits_t *)0x0000000100008208
(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
| (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 }
(lldb) expr property_array_t $ppA = (*$b).properties()
(lldb) p/x $ppA (property_array_t) { list_array_tt<property_t, property_list_t, RawPtr> = { storage = (_value = 0x0000000100008090) } }
(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
| expr class_rw_t *$rw = $bits->data()
(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
定义的属性,会存在属性列表中,里面不包含成员变量。