类的原理分析 // MARK: OC对象之对象的本质
类的结构 首先查看类的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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; ... }
bits 分析 存储着类的方法、属性、协议、成员变量等等 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 struct class_data_bits_t { friend objc_class; uintptr_t bits; ... public : class_rw_t * data () const { return (class_rw_t *)(bits & FAST_DATA_MASK); } const class_ro_t *safe_ro () const { class_rw_t *maybe_rw = data (); if (maybe_rw->flags & RW_REALIZED) { return maybe_rw->ro (); } else { return (class_ro_t *)maybe_rw; } } ... };
可以看出 class_data_bits_t
中有 class_rw_t 和 class_ro_t。 ro 是 readonly ,也就是只读数据。rw 是 可读可写数据。 在wwdc中有提到 ro 数据是 clean memory, 也就是干净的数据,可以被释放,需要的时候可随时从硬盘中读取,rw 是 dirty memory,也就是脏数据,存储着运行中产生的一些数据。
class_ro_t class_ro_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 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; void *baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; const uint8_t * weakIvarLayout; property_list_t *baseProperties; }
class_rw_t ObjC 类中的属性、方法还有遵循的协议等信息都保存在 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 const method_array_t methods () 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)->methods; } else { return method_array_t {v.get <const class_ro_t *>(&ro_or_rw_ext)->baseMethods ()}; } } 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 { return property_array_t {v.get <const class_ro_t *>(&ro_or_rw_ext)->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 { return protocol_array_t {v.get <const class_ro_t *>(&ro_or_rw_ext)->baseProtocols}; } }
细看两个结构体的成员变量会发现很多相同的地方,他们都存放着当前类的属性、实例变量、方法、协议等等。区别在于:class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定,它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_t是class_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容。
通过地址方法类的数据 打印类的内存
1 2 3 4 x/4gx YCPerson.class -------------------- 0x100008198: 0x0000000100008170 0x000000010036e140 0x1000081a8: 0x00000001003663a0 0x0000801c00000000
通过源码可知,按内存排列第二个8字节存储的是superclass,验证一下
1 2 3 po 0x000000010036e140 --------------------- NSObject
查看一下 cache_t 的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct cache_t {private : explicit_atomic<uintptr_t > _bucketsAndMaybeMask; union { struct { explicit_atomic<mask_t > _maybeMask; #if __LP64__ uint16_t _flags; #endif uint16_t _occupied; }; explicit_atomic<preopt_cache_t *> _originalPreoptCache; }; }
由此可知 cache_t 内存大小为 16字节综上 cache 的地址偏移为 16 字节,bits 的地址偏移为32位
使用 lldb 查看一下(代码接上面代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 (lldb) p/x 0x100008198+0x20 # 首先将地址+32位计算出bits的地址 (long) $1 = 0x00000001000081b8 (lldb) p (class_data_bits_t *)$1 # 强转类型为 class_data_bits_t * (class_data_bits_t *) $2 = 0x00000001000081b8 (lldb) p $2->data() # 获取一下 bits 的 data 方法 (class_rw_t *) $3 = 0x0000000100706950 (lldb) p *$3 # 打印一下,可以打印出正确的数据,说明地址偏移是没问题的 (class_rw_t) $4 = { flags = 2148007936 witness = 0 ro_or_rw_ext = { std::__1::atomic<unsigned long> = { Value = 4295000200 } } firstSubclass = nil nextSiblingClass = 0x00007fff8020fa88 } (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 (lldb) p $4->properties() (const property_array_t) $5 = { list_array_tt<property_t, property_list_t, RawPtr> = { = { list = { ptr = 0x0000000100008148 } arrayAndFlag = 4295000392 } } } (lldb) p $5.list (const RawPtr<property_list_t>) $6 = { ptr = 0x0000000100008148 } (lldb) p $6.ptr (property_list_t *const) $7 = 0x0000000100008148 (lldb) p *$7 (property_list_t) $8 = { entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 1) } (lldb) p $8.get(0) // 通过get方法获取第0个元素 (property_t) $9 = (name = "name", attributes = "T@\"NSString\",C,N,V_name") (lldb)
最终获取到name属性