Runtime之NSObject结构

Runtime

简介

OC是一门面向对象的语言,而对于面向对象语言来说一切皆对象。相信每个iOS开发者都很清楚在OC中NSObject是绝大多数对象的父类。OC是一门动态语言,而动态的实现则是离不开Runtime。那么OC中的对象在Runtime中又是以一种什么样的形态出现的呢?本篇文章我们来详细介绍OC对象在Runtime中的结构。

NSObject

我们先来看下在RuntimeNSObject的结构

1
2
3
4
5
6
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

从代码中我们可以看到,实际上对于一个NSObject对象 首先遵守了<NSObject>协议同时有一个isa指针,这个指针是Class类型的。

那么Class的结构呢?

1
typedef struct objc_class *Class;

我们可以简单的理解为:Class是一个指向objc_class类型结构体的指针

那么objc_class的结构又是什么样的呢?

对于这个结构首先,我们在runtime.h中找到了下面这个定义

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

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

} OBJC2_UNAVAILABLE;

这个定义应该是我们在平时查看objc_class结构的时候最长见到的结构了,但是我们注意后面实际上有OBJC2_UNAVAILABLE这个标识,表示在objc2中已经不可用了。因此这个结构不具有参考价值,我们继续来查找objc_class结构体。

objc-runtime-new.h文件中我们发现了新的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// OC 中对象的结构体
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
// bits用于存储类名、类版本号、方法列表、协议列表等信息,替代了Objective-C1.0中methodLists、protocols等成员变量。
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
// class_rw_t 表示 class 是readwrite的 class_ro_t 表示class是readonly的
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
}
// ....省略一些方法

从上面的结构中我们可以看到:类实际上也是一个对象(类对象),主要包括superclasscachebits三个属性。下面我们分别来看下这三个属性的意义。

superclass 父类

superclass首先也是一个class类型,表示这个类的父类。

superclassobjc_class结构中除了标识这个类对象是哪种类型的之外,还用在了下面几个方法中

isRootClass 根类
1
2
3
bool isRootClass() {
return superclass == nil;
}

当superclass为nil时则表明当前的类对象没有父类 同时也就意味着这个类是rootClass。

cache 缓存

cache即缓存,那么缓存中存放的内容是什么呢?我们先来看下cache_t的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
struct cache_t {
// 是一个指向 bucket_t 结构体的哈希表
struct bucket_t *_buckets;
// 是一个 uint32_t 的指针,表示整个 _buckets 哈希表的长度
mask_t _mask;
// _occupied 也是一个 uint32_t 的指针,在 _buckets 哈希表中已经缓存的方法数量

public:
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
//.....
};

cache_t结构中主要包含了一个bucket_t类型的数组_buckets。我们继续看下bucket_t的结构。

bucket_t 方法缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
MethodCacheIMP _imp;
cache_key_t _key;
#else
// unsigned long 的指针,其实是一个被 hash 化的一串数值,就是方法的 sel
cache_key_t _key;
// 保存着对应的函数地址
MethodCacheIMP _imp;
#endif

public:
inline cache_key_t key() const { return _key; }
inline IMP imp() const { return (IMP)_imp; }
inline void setKey(cache_key_t newKey) { _key = newKey; }
inline void setImp(IMP newImp) { _imp = newImp; }

void set(cache_key_t newKey, IMP newImp);
};

每个bucket_t实际上有一个key和imp(暂时不考虑为何在arm64和armv7e i386以及x86之间的顺序区别),并包含了设置key,imp以及取值的方法。那么这个缓存在什么时间被填充和使用呢?

实际上在oc中每个类的方法调用都维护了一个cache,当A类的a方法被调用时,A类的cache存放了a方法的实现和key,如果下次再调用了a方法,那么直接从缓存中取出执行。如果缓存中没有找到,那么在去类的方法列表中进行查找,如果找到了,那么执行这个方法并将方法塞到缓存中,方便下此调用。

bits

同样我们首先看下这个属性的类型为class_data_bits_t,我们再看下这个类型的结构

class_data_bits_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
struct class_data_bits_t {

// Values are the FAST_ flags above.
uintptr_t bits;
private:
bool getBit(uintptr_t bit)
{
return bits & bit;
}
/// ......省略
public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
assert(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
// Set during realization or construction only. No locking needed.
// Use a store-release fence because there may be concurrent
// readers of data and data's contents.
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
}

class_data_bits_t结构体中只有一个64位的指针bits,它相当于 class_rw_t 指针加上 rr/alloc 等标志位。其中class_rw_t指针存在于4~47位(从1开始计)

is_swift标记位标示是否为swift的类。

class_data_bits_t结构体中,我们看到有一个data的setter和getter方法。

我们先来看下设置方法的实现:

setData(class_rw_t *newData)
1
2
3
4
5
6
// (bits & ~FAST_DATA_MASK)现将当前存储的值bits与之前定义好的掩码进行按位与操作 相当于取出标志位 3-46位
// 将前一步得到的值与外部传入的值进行按位或操作 设置新的标志位的值
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
// 将操作后的结果保存着唉bits中
bits = newBits;

带着这个我们在去看getter方法:

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

先通过与掩码进行按位与操作 获取到所有的标志位 然后返回。

类似的标志位还有:

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

// class or superclass has .cxx_construct implementation
// 第 18 位的值是否为 1,以此表示该类或者父类是否有 .cxx_construct 函数实现
#define RW_HAS_CXX_CTOR (1<<18)
// class or superclass has .cxx_destruct implementation
// 第17位的值是否为 1 一次来表示类或者父类有 .cxx_destruct 函数实现。
#define RW_HAS_CXX_DTOR (1<<17)

// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
// 第 16 位的值是否为 1,以此表示该类或者父类是否有 alloc/allocWithZone 函数的默认实现
#define RW_HAS_DEFAULT_AWZ (1<<16)
// class's instances requires raw isa
// 第 15 位的值是否为 1,以此表示类实例对象(此处是指类对象,不是使用类构建的实例对象,一定要记得)是否需要原始的 isa。
#if SUPPORT_NONPOINTER_ISA
#define RW_REQUIRES_RAW_ISA (1<<15)
#endif

// class or superclass has default retain/release/autorelease/retainCount/
// _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
// 第 14 位的值是否为 1,以此表示该类或者父类是否有如下函数的默认实现
#define RW_HAS_DEFAULT_RR (1<<14)

// class is a Swift class from the pre-stable Swift ABI
// class 是来自稳定的 Swift ABI 的 Swift 类。(遗留的类)
#define FAST_IS_SWIFT_LEGACY (1UL<<0)

// class is a Swift class from the stable Swift ABI
// class 是一个有稳定的 Swift ABI 的 Swift类。
#define FAST_IS_SWIFT_STABLE (1UL<<1)
// data pointer
// 一个定义好的掩码 二进制第 3-46 位是 1,其他位都是 0
#define FAST_DATA_MASK 0xfffffffcUL

我们可以看到在getter方法中返回的实际上是一个class_rw_t结构类型的指针,那么这个结构体的结构是什么样的呢?

class_rw_t
1
2
3
4
5
6
7
8
9
10
struct class_rw_t {
// 只读class 结构体
const class_ro_t *ro;
//方法列表
method_array_t methods;
//属性列表
property_array_t properties;
//协议列表
protocol_array_t protocols;
}

这个结构体中我们发现了一个class_ro_t的常量,class_ro_tclass_rw_t的最大区别在于一个是只读的,一个是可读写的,实质上ro就是readonly的简写,rwreadwrite的简写。

对于method_array_t methodsproperty_array_t propertiesprotocol_array_t protocols我们可以简单的从命名上看出分别对应 方法列表、属性列表、协议列表。

我们在进一步看下class_ro_t的结构体:

class_ro_t
1
2
3
4
5
6
7
8
9
10
struct class_ro_t {
//
const char * name;
// 方法列表
method_list_t * baseMethodList;
// 协议列表
protocol_list_t * baseProtocols;
//成员变量列表
const ivar_list_t * ivars;
};

结构与class_rw_t类似分别包含了只读的方法列表、协议列表和成员变量列表。

那么class_rw_tclass_ro_t的区别和关系是什么呢?

  • class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定
  • class_rw_oclass_rw_t中的一个属性,所以可以说class_rw_tclass_ro_t的超集
  • 在程序运行期间实际上我们访问的方法、属性、协议的列表都是访问class_rw_t中的

那么class_rw_t是如何被创建的,他的class_ro_t属性又是如何被赋值的呢?
当dyld调用load_images将镜像加载到内存后,然后依次会map_images->_read_images->realizeAllClasses->realizeClass
我们来看下objc-runtime-new.mrealizeClass方法中的一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
ro = (const class_ro_t *)cls->data();
//是否已经初始化过,初始化过的哈 则 cls->rw 已经初始化过
if (ro->flags & RO_FUTURE) {
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// 正常情况下 申请class_rw_t空间
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;//cls->rw->ro 指向现在的ro
rw->flags = RW_REALIZED|RW_REALIZING;//realized = 1 and realizing = 1
cls->setData(rw);//赋值
}

从上面代码中我们可以看到 cls->data() 最开始实际上是readonly的,我们在类初始化的时候,创建一个rw,然后将ro的值赋值给rw->ro,然后将rw的标志位赋值。最后赋值给cls->rw

这个过程我们可以通过下图更加详细的区分,首先是编译过程中:

在runtime执行了realizeClass之后

总结

这样我们就把NSObject在runtime中相关的结构体都看了一遍,通过上面的了解我们来重新看下NSObject的结构: