李峰峰博客

OC 对象一探究竟之一:对象的底层实现

2019-11-21

一、NSObject 对象

1、NSObject 的底层实现

我们在 iOS 开发过程中,所编写的 Objective-C 代码,其底层实现都是使用的 C\C++ 代码,所以 Objective-C 的面向对象都是基于 C\C++ 的数据结构实现的。

例如,对于以下代码(main.m):

1
2
3
4
5
6
7
8
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
}
return 0;
}

我们进到 NSObject.h 可以看到 NSObject 的结构如下:

1
2
3
@interface NSObject <NSObject> {
Class isa;
}

我们可以使用以下命令将 Objective-C 代码(main.m)转换为 C\C++ 代码(main.cpp):

1
clang -rewrite-objc main.m

(等价于:clang -rewrite-objc main.m -o main.cpp)

但是有些 OC 代码要转成 C/C++ 代码时,在真机、模拟器、不同架构之间可能会存在较大差异。所以可以结合 xcrun 命令指定真机以及架构:

1
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m  -o main.cpp

在使用 clang 转换 OC 为 C++ 代码时,可能会遇到以下问题:

1
cannot create __weak reference in file using manual reference

解决方案:指定支持 ARC、运行时系统版本,比如:

1
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m  -o  main.cpp

转换成 cpp 文件之后,我们可以找到 NSObject 的实现:

1
2
3
struct NSObject_IMPL {
Class isa;
};


由此,我们可以确定,NSObject 对象,实际上就是 C/C++ 的结构体。

在上面 NSObject_IMPL 结构体中,有一个 Class 类型的成员 isa,那么 Class 又是什么呢?我们可以在我们生成的 main.cpp 中看到:

1
typedef struct objc_class *Class;


所以,Class 是指向 objc_class 结构体的指针。

2、对象的分类

对象主要有以下类型:instance 对象、class 对象、meta-class 对象

(1) instance 对象

instance 对象即实例对象,也是开发者接触最多的一个对象。实例对象中不存储方法,只存储成员变量。instance 在内存中存储了如下信息:

  • isa 指针
  • 其他成员变量

isa 指针在 instance 对象中所有成员变量的第一位,是第一个成员变量。所以,isa 指针在内存中的地址就是当前 instance 对象的地址。

instance 对象中没有存储方法,方法实际上存储在 class 对象和 meta-class 对象中的。class 对象中存储了实例方法,meta-class 对象中存储了类方法。

为什么 instance 对象中不存储方法?原因很简单,一个类可能会被创建无数个实例对象,每个实例对象中都存储一份相同的方法信息,是对内存的一种浪费。而对于成员变量来说,每个实例对象的成员变量可能被赋不同值,所以是必须每个实例对象要存储自己的成员变量信息。

(2) class 对象

class 对象即类对象,每个类在内存中有且只有一个 class 对象,创建类对象的方法:

1
2
3
Class classObject1 = [instanceObject class];
Class classObject2 = object_getClass(instanceObject);
Class classObject3 = [NSObject class];

对于同一个 Class 类型,创建出来的所有 class 对象实际上都是同一个,可以验证一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
NSObject *instanceObject1 = [[NSObject alloc] init];
NSObject *instanceObject2 = [[NSObject alloc] init];

Class classObject1 = [instanceObject1 class];
Class classObject2 = [instanceObject2 class];
Class classObject3 = object_getClass(instanceObject1); // 接收的参数是实例对象
Class classObject4 = object_getClass(instanceObject2);// 接收的参数是实例对象
Class classObject5 = [NSObject class];

NSLog(@"instanceObject1=%p\ninstanceObject2=%p",
instanceObject1,
instanceObject2);

NSLog(@"classObject1=%p\nclassObject2=%p\nclassObject3=%p\nclassObject4=%p\nclassObject5=%p",
classObject1,
classObject2,
classObject3,
classObject4,
classObject5);

打印结果:

1
2
3
4
5
6
7
instanceObject1=0x100507f30
instanceObject2=0x100507f40
classObject1=0x7fff915dc118
classObject2=0x7fff915dc118
classObject3=0x7fff915dc118
classObject4=0x7fff915dc118
classObject5=0x7fff915dc118

可以发现,创建的多个 class 对象,内存地址都是一样的。

class 对象在内存中主要存储了如下信息:

  • isa 指针
  • superclass 指针
  • 类的属性信息(@property)、类的对象方法信息(instance method)
  • 类的协议信息(protocol)、类的成员变量信息(ivar)
  • ……

此处的类的成员变量信息指的是:成员变量的描述信息(类型、变量名等)

这里需要注意的是,其中存储的方法信息为对象方法信息,而不是类方法信息。对象的方法并没有直接存储于对象的结构体中,是因为如果每一个对象都保存了自己能执行的方法,那么对内存的占用有极大的影响。

(3) meta-class 对象

meta-class 对象即元类对象,每个类在内存中有且只有一个 meta-class 对象,创建 meta-class 对象方法:

1
Class metaClassObject = object_getClass(classObject); // 接收的参数是类对象

例如:

1
Class metaClassObject = object_getClass([NSObject class]);

meta-class 对象和 class 对象的内存结构是一样的,只不过跟 class 对象相比有些字段是空的,例如属性信息、对象方法信息等这些相应字段 value 是空的。
meta-class 对象在内存中存储的信息主要包括:

  • isa 指针
  • superclass 指针
  • 类的类方法信息(class method)
  • ……

类方法就存储在 meta-class 对象中。

二、isa 指针和 superclass 指针

1、isa 指针

根据前面内容可以知道,当我们创建了一个 instance 对象时,instance 对象中存储了成员变量,对象方法存储在对应的 class 对象中,类方法存储在对应的 meta-class 对象中。当我们调用这个 instance 对象的对象方法或者类方法时,肯定就需要某种机制,将这些相应的对象关联起来,以保证可以正常调用到对应方法,这就是 isa 指针的作用。

  • instance 的 isa 指向 class
    当调用对象方法时,通过 instance 的 isa 找到 class,最后找到对象方法的实现进行调用。

  • class 的 isa 指向 meta-class
    当调用类方法时,通过 class 的 isa 找到 meta-class,最后找到类方法的实现进行调用。

2、superclass 指针

(1) class 对象的 superclass 指针

对于 superclass 指针,假设有下面两个类:

1
2
3
4
5
6
7
// Person 继承自 NSObject
@interface Person : NSObject
@end

// Student 继承自 Person
@interface Student : Person
@end

这时候会存在 3 个主要的 class 对象:Student、Person、NSObject,假设这时候,Student 的 instance 对象想要调用 Person 的某个对象方法时,对象是如何找到 Person 以及对应的对象方法呢?根据 superclass 名字也很容易猜到,superclass 指针指向自己父类的 class 对象:

  • instance 对象中没有 superclass 指针。
  • class 对象的 superclass 指针指向父类的 class 对象。
  • 当 Student 的 instance 对象要调用 Person 的对象方法时,会先通过 isa 找到 Student 的 class,然后通过 superclass 找到 Person 的 class,最后找到对象方法的实现进行调用。

(2) meta-class 对象的 superclass 指针

还以上面 Student 、Person 为例,如果 Student 的 class 要调用 Person 的类方法时,那如何找到对应方法呢?

meta-class 对象的 superclass 指针指向父类的 meta-class 对象,当 Student 的 class 要调用 Person 的类方法时,会先通过 isa 找到 Student 的 meta-class,然后通过 superclass 找到 Person 的 meta-class,最后找到类方法的实现进行调用。

3、总结


上面是一个非常经典的图,从上图可知:

  • 在实现中,Root Class 就是 NSObject
  • NSObject 的 meta-class 父类是 NSObject 类
  • instance 的 isa 指向 class
  • class 的 isa 指向 meta-class
  • meta-class 的 isa 指向基类的 meta-class
  • 基类的 meta-class 的 isa 指向它自己
  • class 的 superclass 指向父类的 class,如果没有父类,superclass 指针为 nil
  • meta-class 的 superclass 指向父类的 meta-class
  • 基类的 meta-class 的 superclass 指向基类的 class
  • instance 调用对象方法的路径:isa 找到 class,方法不存在,就通过 superclass 找父类
  • class 调用类方法的路径:isa 找 meta-class,方法不存在,就通过 superclass 找父类

三、isa 的结构及实现

1、isa 的结构

在 Objc 2.0 之前,objc_class 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

2006 年发布 Objc 2.0 之后,objc_class 的定义如下:

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
typedef struct objc_class *Class;
typedef struct objc_object *id;

@interface Object {
Class isa;
}

@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}

struct objc_object {
private:
isa_t isa;
}

struct objc_class : objc_object {
// 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
}

union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
}

objc_class 继承于 objc_object。所以在 objc_class 中也会包含 isa_t 类型的结构体 isa。至此,可以得出结论:Objective-C 中类也是一个对象。在 objc_class 中,除了 isa 之外,还有 3 个成员变量,分别是:

  • super_class
    指向当前类的父类
  • cache
    用于缓存指针和 vtable,加速方法的调用
  • bits
    就是存储类的方法、属性和遵循的协议等信息的地方

对 objc_class 进行简化,其结构体实际上就是下面样子:

1
2
3
4
5
6
struct objc_class : objc_object {
isa_t isa;
Class superclass;
cache_t cache;
class_data_bits_t bits;
};

前面提到 NSObject 中也有个 isa,NSObject 中 isa 与 objc_class 中 isa 关系如下图:

objc_class 中的 isa 是 isa_t 类型的,isa_t 是一个联合体(union)

联合体
联合体与结构体非常类似,主要区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而联合体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。

位域
位域定义与结构定义相仿,其形式为:
struct 位域结构名
{ 位域列表 };

其中位域列表的形式为:
类型说明符 位域名:位域长度

例如:

1
2
3
4
5
6
struct bits
{
int a:8;
int b:2;
int c:6;
}data;

说明 data 为 bits 结构体变量,共占两个字节(1 个字节存储 8 位无符号数)。其中位域 a 占 8 位,位域 b 占 2 位,位域 c 占 6 位。

isa_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
49
50
51
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD;
};
#endif
};


# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)

# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)

# else
# error unknown architecture for packed isa
# endif

也就是说,在 64 位环境下,isa_t 就是下面这个样子(后续都将以 64 位为例):

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
#define ISA_MASK        0x00007ffffffffff8ULL
#define ISA_MAGIC_MASK 0x001f800000000001ULL
#define ISA_MAGIC_VALUE 0x001d800000000001ULL
#define RC_ONE (1ULL<<56)
#define RC_HALF (1ULL<<7)

union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

Class cls;
uintptr_t bits;

struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33;
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19
};
};

所以,也可以理解为 isa_t 的结构是联合体+位域。
isa_t 内存结构如下图:

各参数解释如下:

2、isa_t 的实现

以下是 objc_object 结构体的部分内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct objc_object {
private:
isa_t isa;
public:
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
private:
void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor);

当系统通过 alloc 为一个对象分配内存时,会同时初始化 isa。对于对象来说,isa 的基础作用就是将对象和类进行绑定,告诉系统对象的归属。

初始化 isa 主要是调用下面这两个方法(已精简处理):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
initIsa(cls, true, hasCxxDtor);
}

inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
isa_t newisa(0);

newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;

isa = newisa;
}
}

可以看到,initIsa 中间首先有个断言,如果对象是 Tagged Pointer 就不继续执行了,这里涉及到了 Tagged Pointer,那什么是 Tagged Pointer 呢?

以 NSNumber 对象为例,一个 NSNumber 对象,值是一个整数。正常情况下,如果这个整数只是一个 NSInteger 的普通变量,那么它所占用的内存是与 CPU 的位数有关,在 32 位 CPU 下占 4 个字节,在 64 位 CPU 下是占 8 个字节的。一个指针所占用的内存在 32 位 CPU 下为 4 个字节,在 64 位 CPU 下也是 8 个字节。

如果没有 Tagged Pointer 对象,32 位和 64 位下这个 NSNumber 内存占用情况如下:

可以看出,如果没有 Tagged Pointer 对象,64 位设备相较于 32 位设备,NSNumber、NSDate 这样的对象所占用的内存会翻倍。不仅仅是内存上的浪费,作为一个对象,我们还需要在堆上为其分配内存、维护它的引用计数、管理它的生命期。这些都给程序增加了额外的逻辑,造成运行效率上的损失。

在 2013 年 9 月,苹果推出了 iPhone5s,与此同时,iPhone5s 配备了首个采用 64 位架构的 A7 双核处理器,为了节省内存和提高执行效率,苹果提出了 Tagged Pointer 的概念。对于 64 位程序,引入 Tagged Pointer 后,相关逻辑能减少一半的内存占用,以及 3 倍的访问速度提升,100 倍的创建、销毁速度提升。

由于 NSNumber、NSDate 一类的变量本身的值需要占用的内存大小常常不需要 8 个字节,拿整数来说,4 个字节所能表示的有符号整数就可以达到 20 多亿(注:2^31=2147483648,另外 1 位作为符号位),对于绝大多数情况都是可以处理的。

所以可以将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址。所以,引入了 Tagged Pointer 对象之后,64 位 CPU 下 NSNumber 的内存占用变成了下面这样:

所以,Tagged Pointer 指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要 malloc 和 free。

接下来看一个 Tagged Pointer 例子,下面代码,执行结果是什么?

1
2
3
4
5
6
7
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghijk"];
});
}

执行上面代码发现会必现 crash,因为这里 self.name 是正常 NSString 对象,其本质在 setter 方法中必然会对旧值有 release 操作,由于是在子线程赋值,很大概率出现同时 release,这就导致 crash 发生。

而下面代码执行却不会出现 crash:

1
2
3
4
5
6
7
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abc"];
});
}

因为在上面代码中,给 self.name 赋的值是一个 Tagged Pointer,不需要 release,所以不会出现前面同时 release 引发的 crash。

以上就是关于 Tagged Pointer 相关内容,接下来回到对 initIsa 方法的分析,再看下 initIsa 函数的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
isa_t newisa(0);

newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;

isa = newisa;
}
}

可以看到在判断了非 Tagged Pointer 之后,又判断了 nonpointer 是否为 true,前面提到 nonpointer 含义是是否开启了 isa指针 优化,1 代表优化过,0 代表未优化。

根据源码也可以看到,在早期,也就是未进行 isa 指针优化前,isa 直接指向了 class 的地址。优化后,isa 内部存储了更加多的信息,并且不再直接指向 class 地址(isa 地址与 ISA_MASK 进行位运算后,才是 class 地址)。

在 initInstanceIsa 方法中,调用 initIsa 方法的时候 nonpointer= true,所以我们可以将方法简化为:

1
2
3
4
5
6
inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{
isa.bits = ISA_MAGIC_VALUE;
isa.has_cxx_dtor = hasCxxDtor;
isa.shiftcls = (uintptr_t)cls >> 3;
}

ISA_MAGIC_VALUE = 0x000001a000000001ULL 对应二进制是11010000000000000000000000000000000000001,当对 bits 赋值之后,isa_t 的变化如下图:

从上图可知,这一步对 nonpointer 和 magic 进行了赋值。可以看到 nonpointer 被赋值为 1。

在设置 nonpointer 和 magic 值之后,会设置 isa 的 has_cxx_dtor,这一位表示当前对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存。

1
isa.has_cxx_dtor = hasCxxDtor;

最后就要将当前对象对应的类指针存入 isa 结构体中了:

1
isa.shiftcls = (uintptr_t)cls >> 3;

由于类的指针要按照字节(8 bits)对齐内存(关于字节对齐下篇文章会专门分析),其指针后三位肯定都是没有意义的 0。将当前地址右移三位的就是为了将 Class 指针中无用的后三位清除,以减小内存的浪费,为 isa 留下更多空间用于性能的优化。

对于 isa 和对应的 Class 对象之间关系,我们都知道可以使用 object_getClass(obj) 获取 Class 对象,object_getClass(obj) 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}

inline Class
objc_object::getIsa()
{
return ISA();
}

inline Class
objc_object::ISA()
{
return (Class)(isa.bits & ISA_MASK);
}

由以上源码可知,isa 地址,经过与 ISA_MASK 进行位运算,就是对应 class 或 meta-class 地址。

实际上,从 64bit 开始,isa 不再直接指向 class 或 meta-class 地址,而是需要 isa 地址与 ISA_MASK 进行一次位运算后,才是 class 或 meta-class 的地址。

3、拾遗:objc_class 中的 cache、bits

再回头看 objc_class 的结构:

1
2
3
4
5
6
struct objc_class : objc_object {
isa_t isa;
Class superclass;
cache_t cache;
class_data_bits_t bits;
};

其中 bits 就是存储类的方法、属性和遵循的协议等信息的地方,在 objc_class 结构体中的注释写到 class_data_bits_t 相当于 class_rw_t 指针加上 rr/alloc 的标志。

1
class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

它为我们提供了便捷方法用于返回其中的 class_rw_t * 指针:

1
2
3
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}

将 bits 与 FAST_DATA_MASK 进行位运算,转换成 class_rw_t * 返回。ObjC 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct class_rw_t {
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;
};

其中还有一个指向常量的指针 ro,其中存储了当前类在编译期就已经确定的属性、方法以及遵循的协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
uint32_t reserved;

const uint8_t * ivarLayout;

const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;

const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};

使用下图表示其关系:

对于 objc_class 结构体中的 cache,作用主要是为了优化方法调用的性能。当对象 receiver 调用方法 message 时,首先根据对象 receiver 的 isa 指针查找到它对应的类,然后在类的 methods 中搜索方法,如果没有找到,就使用 super_class 指针到父类中的 methods 查找,一旦找到就调用方法。如果没有找到,有可能消息转发,也可能忽略它。但这样查找方式效率太低,因为往往一个类大概只有 20% 的方法经常被调用,占总调用次数的 80%。所以使用 Cache 来缓存经常调用的方法,当调用方法时,优先在 Cache 查找,如果没有找到,再到 methods 查找。