一个对象中,可以有属性,有方法,那么对象的本质是什么呢?类的本质又是什么呢?属性和方法又是存储在哪里呢?这就是我们今天探索的主题。
必要知识 在前篇的探索 揭开 isa 的神秘面纱 中,我们了解到每个类都有一个 isa,里面存储了该对象所属的类的信息。并且找到了对象和类在 C/C++ 层面的实现。
1 2 3 4 5 6 7 struct objc_object {private : isa_t isa; public : }
1 typedef struct objc_object *id ;
上面是 对象
在底层的实现,也找到了 id 类型的声明,这也是为什么 id 可以指向任何对象的根本原因。
1 2 3 4 5 6 7 struct objc_class : objc_object { Class superclass; cache_t cache; class_data_bits_t bits; }
这个是 类
在底层的实现,它继承于 objc_object
,所以 objc_class
也是一个对象,被称为类对象。我们常用的 Class 就是 objc_class
的 typedef。
1 typedef struct objc_class *Class ;
探究 『继承』 在 objc_class
中有个属性 superclass
, 我们先探究下 superclass
的继承体系。
如下,我们分别定义了两个类,RDPerson 继承于 NSObject,RDTeacher 继承于 RDPerson 。
1 2 3 4 5 6 7 @interface RDPerson : NSObject @end @implementation RDPerson @end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @interface RDTeacher : RDPerson @property (nonatomic , copy ) NSString * name;@property (nonatomic , copy ) NSString * hobby;@property (nonatomic , copy ) NSString * address;+ (void )sayStandUp; - (void )sayByebye; @end @implementation RDTeacher + (void )sayStandUp { NSLog (@"Stand up" ); } - (void )sayByebye { NSLog (@"Good bye" ); } @end
如图,我们实例化一个对象,在 NSLog 的地方打个断点,在 Debug area
中就可以使用 lldb
指令进行探索了。
使用 x/8gx
查看当前对象地址开始的 8 个 8 字节的数据。 对象 t 首地址中存储的是 isa,后面的依次是对象的每个属性。看看是否是我们设置的数据。
1 2 3 4 5 6 7 8 9 10 11 (lldb) po 0x001d800100003465 & 0x00007ffffffffff8 ULL RDTeacher (lldb) po 0x0000000100002058 Lucy (lldb) po 0x0000000100002078 Men (lldb) po 0x0000000100002098 Earth
接下来探索 isa 中的类对象的信息。用 p/x
打印出当前对象归属的 RDTeacher
类的地址 A(16进制), 也就是当前对象中的 isa
中 shiftcls
的数据。
1 2 (lldb) p/x 0x001d800100003465 & 0x00007ffffffffff8 ULL (unsigned long long ) $5 = 0x0000000100003460
用 x/4gx
读取地址 A 中的内容,并用 po
打印出当前类的 superclass。根据前面的 objc_class 的属性分析,第二个 8 字节存储的就是 superclass。
1 2 3 4 5 6 7 8 9 (lldb) x/4 gx 0x0000000100003460 0x100003460 : 0x0000000100003438 0x0000000100003500 0x100003470 : 0x00000001016123a0 0x0002802c00000007 (lldb) po 0x0000000100003438 & 0x00007ffffffffff8 ULL RDTeacher (lldb) po 0x0000000100003500 RDPerson
可以看出 RDTeacher 的父类是 RDPerson,它的地址是 0x0000000100003500
。用 x/4gx
读取地址 0x0000000100003500
中的内容,然后打印 RDPerson
的 superclass
。
1 2 3 4 5 6 (lldb) x/4 gx 0x0000000100003500 0x100003500 : 0x00000001000034d8 0x00000001003f1140 0x100003510 : 0x00000001003eb450 0x0000801000000000 (lldb) po 0x00000001003f1140 NSObject
可以看出 RDPerson 的父类是 NSObject
,它的地址是 0x00000001003f1140
。 用 x/4gx
读取 0x00000001003f1140
中的内容,然后打印 NSObject
的 superclass
。
1 2 3 4 5 6 (lldb) x/4 gx 0x00000001003f1140 0x1003f1140 : 0x00000001003f10f0 0x0000000000000000 0x1003f1150 : 0x00000001016120c0 0x0001801000000003 (lldb) po 0x0000000000000000 <nil>
可以看出 NSObject
的父类是 nil
。
所以我们可以得出 RDTeacher 的继承关系。
探究 isa 指向 对象 t 的内存地址中的数据如下所示:
1 2 3 4 5 6 (lldb) p t (RDTeacher *) $81 = 0x0000000101612260 (lldb) x/4 gx 0x0000000101612260 0x101612260 : 0x001d800100003465 0x0000000100002058 0x101612270 : 0x0000000100002078 0x0000000100002098
接下来探索对象 t 的 isa 指向的是啥?首先用 p/x
和 0x00007ffffffffff8ULL
mask 出 isa 中的 shiftcls
。然后用 po
打印 shiftcls
的内容。
1 2 3 4 5 (lldb) p/x 0x001d800100003465 & 0x00007ffffffffff8ULL (unsigned long long) $104 = 0x0000000100003460 (lldb) po 0x0000000100003460 RDTeacher
所以对象 t 的 isa
指向的类是 RDTeacher
类。接下来我们探索 RDTeacher
的 isa
指向的是啥。
先用 x/4gx
读出内存 0x0000000100003460
内容,然后用 p/x
和 0x00007ffffffffff8ULL
mask 出 RDTeacher
类对象所属的类的地址。
最后用 x/4gx
读取这个地址中的内容,用 po 打印当前 isa 中的类。类对象所属的类称为 元类
(meta class)。
1 2 3 4 5 6 7 8 9 (lldb) x/4 gx 0x0000000100003460 0x100003460 : 0x0000000100003438 0x0000000100003500 0x100003470 : 0x0000000100668c20 0x0004802c0000000f (lldb) p/x 0x0000000100003438 & 0x00007ffffffffff8 ULL (unsigned long long ) $107 = 0x0000000100003438 (lldb) po 0x0000000100003438 RDTeacher
我们通过 object_getClass
方法直接打印 RDTeacher
的元类的地址,看是否等于上面输出的 0x0000000100003438
。
1 2 (lldb) p/x object_getClass (RDTeacher.class) (Class) $109 = 0x0000000100003438
可以看出他们是相等的。也就是说我们得到了类对象的元类。
继续探索 元类 RDTeacher
的 isa
,看它指向了啥。用 x/4gx
读取这个地址中的内容。然后用 p/x
和 po
打印元类的 isa
指向的类的信息。
1 2 3 4 5 6 7 8 9 (lldb) x/4 gx 0x0000000100003438 0x100003438 : 0x00000001003f10f0 0x00000001000034d8 0x100003448 : 0x0000000101204690 0x0004e03500000007 (lldb) p/x 0x00000001003f10f0 & 0x00007ffffffffff8 ULL (unsigned long long ) $112 = 0x00000001003f10f0 (lldb) po 0x00000001003f10f0 NSObject
我们得到的这 NSObject
是我们常用的那个 NSObject
么?也就是所有对象的根类。用 p/x
打印下类对象 NSObject 的地址。
1 2 (lldb) p/x [NSObject class ] (Class ) $115 = 0x00000001003f1140 NSObject
这和 0x00000001003f10f0
是不相等的,我们再打印下 NSObject
类对象的元类的地址。
1 2 (lldb) p/x object_getClass (NSObject.class) (Class) $116 = 0x00000001003f10f0
此时得到的这个值是和 0x00000001003f10f0
是相等的。说明 元类 RDTeacher
的 isa
指向的是 NSObject
的元类,这个元类称之为根元类(root meta class)。
最后我们看看根元类的 superclass
和 isa
指向哪里?用 x/4gx
查看下 0x00000001003f10f0
地址的内容。
1 2 3 (lldb) x/4 gx 0x00000001003f10f0 0x1003f10f0 : 0x00000001003f10f0 0x00000001003f1140 0x1003f1100 : 0x0000000101018bd0 0x0005e03100000007
我们先看看 NSObject 根元类的 superclass
是啥?
1 2 (lldb) po 0x00000001003f1140 NSObject
这个 NSObject
的地址和我们之前的 NSObject 的类对象的地址一样的,都是 0x00000001003f1140
, 所以根元类的父类是 NSObject 根类。
再看一下根元类的 isa
指向啥?用 p/x
和 0x00007ffffffffff8ULL
mask 出根元类所属的类的 isa。
1 2 3 4 5 (lldb) p/x 0x00000001003f10f0 & 0x00007ffffffffff8ULL (unsigned long long) $118 = 0x00000001003f10f0 (lldb) po 0x00000001003f10f0 NSObject
0x00000001003f10f0
这个地址和根元类的地址是一样的,也就是说根元类的 isa 指向了自己本身。
根据以上的探索,我们可以得出一个完整的 isa 指向图。
根据前面探索的继承关系和 isa 走向,我们可以将两张图合并成一个图。
class 的结构 class 中的属性和方法存储在什么地方呢?先回到源码中 objc_class
声明的地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct objc_class : objc_object { Class superclass; cache_t cache; class_data_bits_t bits; class_rw_t *data () const { return bits.data(); } void setData (class_rw_t *newData) { bits.setData(newData); } }
class_rw_t 根据上面的源代码,可以推测 class
的方法和属性应该是在 bits 中,通过 data()
方法可以获取到 bits 中的数据,这个方法的返回值是 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 37 38 39 40 41 42 43 44 45 struct class_rw_t { uint32_t flags; uint16_t witness; #if SUPPORT_INDEXED_ISA uint16_t index; #endif explicit_atomic<uintptr_t > ro_or_rw_ext; Class firstSubclass; Class nextSiblingClass; private : using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>; 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 *>()->methods; } else { return method_array_t {v.get <const class_ro_t *>()->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 *>()->properties; } else { return property_array_t {v.get <const class_ro_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 *>()->protocols; } else { return protocol_array_t {v.get <const class_ro_t *>()->baseProtocols}; } }
在 class_rw_t
的声明中,找到了三个函数。
methods()
用于获取方法列表,它的返回值是 method_array_t
。
properties()
用于获取属性列表,它的返回值是 property_array_t
。
protocols()
用于获取协议列表,它的返回值是 protocol_array_t
。
接下来,我们分别看这三个 array_t 中的元素是什么。
method_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 36 37 38 39 40 41 42 43 44 45 46 47 48 class method_array_t : public list_array_tt<method_t , method_list_t > { typedef list_array_tt<method_t , method_list_t > Super; public : method_array_t () : Super() { } method_array_t (method_list_t *l) : Super(l) { } method_list_t * const *beginCategoryMethodLists () const { return beginLists(); } method_list_t * const *endCategoryMethodLists (Class cls) const ; method_array_t duplicate () { return Super::duplicate<method_array_t >(); } }; struct method_t { SEL name; const char *types; MethodListIMP imp; struct SortBySELAddress : public std ::binary_function<const method_t &, const method_t &, bool > { bool operator () (const method_t & lhs, const method_t & rhs) { return lhs.name < rhs.name; } }; }; struct method_list_t : entsize_list_tt<method_t , method_list_t , 0x3 > { bool isUniqued () const ; bool isFixedUp () const ; void setFixedUp () ; uint32_t indexOfMethod (const method_t *meth) const { uint32_t i = (uint32_t )(((uintptr_t )meth - (uintptr_t )this ) / entsize()); ASSERT(i < count); return i; } };
method_array_t
继承于 list_array_tt
, list_array_tt
是一个类模板,它的声明如下:
1 2 3 4 template <typename Element, typename List>class list_array_tt { }
entsize_list_tt
这是个什么类型呢?看一看它是怎么定义的。
1 2 3 4 5 6 7 8 template <typename Element, typename List, uint32_t FlagMask>struct entsize_list_tt { uint32_t entsizeAndFlags; uint32_t count; Element first; }
可以看出,这是一个类模板,在模板中指定了所需泛型数据类型。
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 class property_array_t : public list_array_tt<property_t , property_list_t > { typedef list_array_tt<property_t , property_list_t > Super; public : property_array_t () : Super() { } property_array_t (property_list_t *l) : Super(l) { } property_array_t duplicate () { return Super::duplicate<property_array_t >(); } }; struct property_t { const char *name; const char *attributes; }; struct property_list_t : entsize_list_tt<property_t , property_list_t , 0 > {};
protocol_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 36 37 38 39 40 41 42 43 44 45 class protocol_array_t : public list_array_tt<protocol_ref_t , protocol_list_t > { typedef list_array_tt<protocol_ref_t , protocol_list_t > Super; public : protocol_array_t () : Super() { } protocol_array_t (protocol_list_t *l) : Super(l) { } protocol_array_t duplicate () { return Super::duplicate<protocol_array_t >(); } }; typedef uintptr_t protocol_ref_t ; struct protocol_list_t { uintptr_t count; protocol_ref_t list [0 ]; size_t byteSize () const { return sizeof (*this ) + count*sizeof (list [0 ]); } protocol_list_t *duplicate () const { return (protocol_list_t *)memdup(this , this ->byteSize()); } typedef protocol_ref_t * iterator; typedef const protocol_ref_t * const_iterator; const_iterator begin () const { return list ; } iterator begin () { return list ; } const_iterator end () const { return list + count; } iterator end () { return list + count; } };
通过上述源代码的分析,可以得到类的结构图为
现在我们用 lldb
调试的方式,验证下 RDTeacher 类中的属性和方法的存储位置。让代码运行到图一的那个断点处。首先找到类对象的地址。
1 2 (lldb) p/x [RDTeacher class ] (Class ) $0 = 0x00000001000034f0 RDTeacher
用 x/8gx
读出该地址起始的 8 段数据。
1 2 3 4 5 (lldb) x/8 gx 0x00000001000034f0 0x1000034f0 : 0x00000001000034c8 0x0000000100003590 0x100003500 : 0x0000000101856a40 0x0002802c00000007 0x100003510 : 0x0000000101856654 0x00000001003350f0 0x100003520 : 0x00000001003350f0 0x000000010032f410
第一个 8 字节数据 0x00000001000034c8
是 isa
,第二个 8 字节数据是 superclass
,第三个数据存储的是 cache
, cache_t
是一个 struct,通过对 struct 中属性的分析,可以得出 cache
占用 16 个字节大小。所以地址 0x100003500
起始的 16 个字节中存储的 cache。
第四个数据是 bits,它的类型是 class_data_bits_t
,class_data_bits_t
是一个 struct,其内部只有一个 uintptr_t
类型的 bits 数据,所以 bits 占用 8 个字节大小。所以地址 0x100003510
起始的 8 个字节中存储的是 bits 数据。
用 p/x
打印出 bits 数据的地址。
1 2 (lldb) p (class_data_bits_t *) 0x100003510 (class_data_bits_t *) $2 = 0x0000000100003510
调用 data()
方法得到 class_rw_t
类型的数据。
1 2 (lldb) p $2 -> data() (class_rw_t *) $3 = 0x0000000101856650
此时我们得到的是 class_rw_t
类型的指针,通过运算符 *
取出地址中的数据。
1 2 3 4 5 6 7 8 9 10 (lldb) p *$3 (class_rw_t ) $4 = { flags = 2148007936 witness = 1 ro_or_rw_ext = { std ::__1::atomic<unsigned long > = 4294980136 } firstSubclass = nil nextSiblingClass = nil }
调用 class_rw_t
中的 properties()
方法得到属性列表。
1 2 3 4 5 6 7 8 9 (lldb) p $4. properties() (const property_array_t ) $5 = { list_array_tt<property_t , property_list_t > = { = { list = 0x00000001000031f0 arrayAndFlag = 4294980080 } } }
遍历属性列表中的元素,看看是否有 name,hobby,address 这三个属性。
1 2 3 4 5 6 7 8 9 10 11 (lldb) p $5.l ist->get (0 ) (property_t ) $8 = (name = "name" , attributes = "T@\"NSString\",C,N,V_name" ) (lldb) p $5.l ist->get (1 ) (property_t ) $9 = (name = "hobby" , attributes = "T@\"NSString\",C,N,V_hobby" ) (lldb) p $5.l ist->get (2 ) (property_t ) $10 = (name = "address" , attributes = "T@\"NSString\",C,N,V_address" ) (lldb) p $5.l ist->get (3 ) Assertion failed: (i < count), function get , file /Users/muhlenxi/xiyinjun/objc-runtime/objc4-781 /runtime/objc-runtime-new .h, line 438.
当我们取第 4 个属性的时候,发生了数组越界操作,崩溃了。因为 RDTeacher
中一共定义了 3 个属性。
调用 class_rw_t
中的 methods()
方法得到方法列表。
1 2 3 4 5 6 7 8 9 (lldb) p $4. methods() (const method_array_t ) $11 = { list_array_tt<method_t , method_list_t > = { = { list = 0x00000001000030c0 arrayAndFlag = 4294979776 } } }
遍历方法列表中的元素,看看有哪些方法呢?
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 (lldb) p $11.l ist->get (0 ) (method_t ) $14 = { name = "sayByebye" types = 0x0000000100001f87 "v16@0:8" imp = 0x00000001000016c0 (HelloWorld`-[RDTeacher sayByebye] at RDTeacher.m:17 ) } (lldb) p $11.l ist->get (1 ) (method_t ) $15 = { name = "hobby" types = 0x0000000100001f8f "@16@0:8" imp = 0x0000000100001760 (HelloWorld`-[RDTeacher hobby] at RDTeacher.h:16 ) } (lldb) p $11.l ist->get (2 ) (method_t ) $16 = { name = "setHobby:" types = 0x0000000100001f97 "v24@0:8@16" imp = 0x0000000100001790 (HelloWorld`-[RDTeacher setHobby:] at RDTeacher.h:16 ) (lldb) p $11.l ist->get (3 ) (method_t ) $17 = { name = ".cxx_destruct" types = 0x0000000100001f87 "v16@0:8" imp = 0x0000000100001840 (HelloWorld`-[RDTeacher .cxx_destruct] at RDTeacher.m:11 ) } (lldb) p $11.l ist->get (4 ) (method_t ) $18 = { name = "name" types = 0x0000000100001f8f "@16@0:8" imp = 0x00000001000016f0 (HelloWorld`-[RDTeacher name] at RDTeacher.h:15 ) } (lldb) p $11.l ist->get (5 ) (method_t ) $19 = { name = "setName:" types = 0x0000000100001f97 "v24@0:8@16" imp = 0x0000000100001720 (HelloWorld`-[RDTeacher setName:] at RDTeacher.h:15 ) } (lldb) p $11.l ist->get (6 ) (method_t ) $20 = { name = "address" types = 0x0000000100001f8f "@16@0:8" imp = 0x00000001000017d0 (HelloWorld`-[RDTeacher address] at RDTeacher.h:17 ) } (lldb) p $11.l ist->get (7 ) (method_t ) $21 = { name = "setAddress:" types = 0x0000000100001f97 "v24@0:8@16" imp = 0x0000000100001800 (HelloWorld`-[RDTeacher setAddress:] at RDTeacher.h:17 ) } (lldb) p $11.l ist->get (8 ) Assertion failed: (i < count), function get , file /Users/muhlenxi/xiyinjun/objc-runtime/objc4-781 /runtime/objc-runtime-new .h, line 438. error: Execution was interrupted, reason: signal SIGABRT. The process has been returned to the state before expression evaluation.
通过上面的打印,可以看出方法列表中不仅有我们定义的 sayByebye
方法,还有编译器生成的 name,hobby,address 这三个属性的 setter 和 getter 方法,没有找到我们定义的 sayStandUp
方法,这是为什么呢?
sayStandUp
是类方法,存储在 RDTeacher
的元类中,同理,按照上面的思路,我们看看元类中的 methods 列表。
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 37 38 39 40 41 42 43 44 45 46 47 (lldb) p object_getClass (RDTeacher.class) (Class) $2 = 0x00000001000034c8 (lldb) x/8 gx 0x00000001000034c8 0x1000034c8 : 0x00000001003350f0 0x0000000100003568 0x1000034d8 : 0x0000000103274dc0 0x0002e03500000007 0x1000034e8 : 0x0000000103274724 0x00000001000034c8 0x1000034f8 : 0x0000000100003590 0x0000000103274d40 (lldb) p (class_data_bits_t *) 0x1000034e8 (class_data_bits_t *) $3 = 0x00000001000034e8 (lldb) p $3 -> data() (class_rw_t *) $4 = 0x0000000103274720 (lldb) p *$4 (class_rw_t ) $5 = { flags = 2684878849 witness = 1 ro_or_rw_ext = { std ::__1::atomic<unsigned long > = 4294979704 } firstSubclass = nil nextSiblingClass = nil } (lldb) p $5. methods() (const method_array_t ) $6 = { list_array_tt<method_t , method_list_t > = { = { list = 0x0000000100003058 arrayAndFlag = 4294979672 } } } (lldb) p $6.l ist->get (0 ) (method_t ) $7 = { name = "sayStandUp" types = 0x0000000100001f87 "v16@0:8" imp = 0x0000000100001690 (HelloWorld`+[RDTeacher sayStandUp] at RDTeacher.m:13 ) } (lldb) p $6.l ist->get (1 ) Assertion failed: (i < count), function get , file /Users/muhlenxi/xiyinjun/objc-runtime/objc4-781 /runtime/objc-runtime-new .h, line 438. error: Execution was interrupted, reason: signal SIGABRT. The process has been returned to the state before expression evaluation.
methods 列表中第一个方法就是 sayStandUp
,符合我们的预期。
今天的图,值得仔细揣摩,或许你能得出一些关于人生的大道理。
后记 我是穆哥,卖码维生的一朵浪花。我们下回见。