简介
OC是一门面向对象的语言,而对于面向对象语言来说一切皆对象
。相信每个iOS开发者都很清楚在OC中NSObject是绝大多数对象的父类。OC是一门动态语言,而动态的实现则是离不开Runtime。那么OC中的对象在Runtime中又是以一种什么样的形态出现的呢?本篇文章我们来详细介绍OC对象在Runtime中的结构。
NSObject
我们先来看下在Runtime
中NSObject
的结构
@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的结构呢?
typedef struct objc_class *Class;
我们可以简单的理解为:Class是一个指向objc_class
类型结构体的指针
那么objc_class
的结构又是什么样的呢?
对于这个结构首先,我们在runtime.h
中找到了下面这个定义
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
文件中我们发现了新的定义:
/// 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);
}
}
// ....省略一些方法
从上面的结构中我们可以看到:类实际上也是一个对象(类对象),主要包括superclass
、cache
、bits
三个属性。下面我们分别来看下这三个属性的意义。
superclass 父类
superclass
首先也是一个class类型,表示这个类的父类。
superclass
在objc_class
结构中除了标识这个类对象是哪种类型的之外,还用在了下面几个方法中
isRootClass 根类
bool isRootClass() {
return superclass == nil;
}
当superclass为nil时则表明当前的类对象没有父类 同时也就意味着这个类是rootClass。
cache 缓存
cache即缓存,那么缓存中存放的内容是什么呢?我们先来看下cache_t
的结构
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 方法缓存
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
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)
// (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()
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
先通过与掩码进行按位与操作 获取到所有的标志位 然后返回。
类似的标志位还有:
// 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
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_t
与class_rw_t
的最大区别在于一个是只读的,一个是可读写的,实质上ro
就是readonly
的简写,rw
是readwrite
的简写。
对于method_array_t methods
、property_array_t properties
、 protocol_array_t protocols
我们可以简单的从命名上看出分别对应 方法列表、属性列表、协议列表。
我们在进一步看下class_ro_t
的结构体:
class_ro_t
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_t
和class_ro_t
的区别和关系是什么呢?
class_ro_t
存放的是编译期间就确定的;而class_rw_t
是在runtime
时才确定class_rw_o
是class_rw_t
中的一个属性,所以可以说class_rw_t
是class_ro_t
的超集- 在程序运行期间实际上我们访问的方法、属性、协议的列表都是访问
class_rw_t
中的
那么class_rw_t
是如何被创建的,他的class_ro_t
属性又是如何被赋值的呢?
当dyld调用load_images将镜像加载到内存后,然后依次会map_images->_read_images->realizeAllClasses->realizeClass
。
我们来看下objc-runtime-new.m
中realizeClass
方法中的一段代码:
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的结构: