很早之前写过图解Runtime系列,那时候比较忙只是画了流程图,当然对自己理解已经够用了,但是对于没有看过源码对同学可能会看不懂,想着一个系列的博客都快完成了,少了这块总觉得缺了什么,并且Runtime是iOS的核心对理解底层有很大帮助,所以找了个空余时间将这块补上了。

我们知道编程语言有静态语言和动态语言之分,静态语言在编译的时候就已经明确了每行代码最终执行哪些代码,Objective C 作为动态语言它的底层是由编译器和Runtime构成,编译时期只是决定向某个对象发送某个消息,但是最终这个对象怎么处理这个消息取决于这个对象而不是固定的,也就是说在编译之后还可以针对发过来的消息对这个消息进行一系列处理最终决定执行哪些代码,这部分工作都交由Runtime处理。在介绍Runtime细节之前我们先看下相关的数据结构,熟悉这些数据结构对理解Runtime来说是必不可少的。本文将以Objc 2.0 中的数据结构为研究对象。

我们上层最经常接触的应该算是Classid这两个类型了,它实际上分别是****objc_class * objc_object * ****的重定义类型。

typedef struct objc_class *Class;
typedef struct objc_object *id;
  • objc_object 结构

objc_object里面有个重要的成员对象isa_t isa,因为objc_object是用来存储对象数据用的,所以不宜将无用的数据存储在objc_object上,所以这里只存放了isa_t isa,通过isa_t isa可以找到objc_class,objc_class中存放的才是这些对象公用的数据。

struct objc_object {
private:
isa_t isa;
//.....
};
  • objc_class 结构

objc_class结构如下所示:

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
//.....
};

我们从它的定义上看,它其实也是一个objc_object,所以objc_class也会有一个isa,它其实指向的是Meta class,Meta class 也是一个objc_class它和objc_object指向objc_class区别在于它的cache以及bits中存放的是类静态方法等数据。这些会在后面介绍。

  • isa_t 结构
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};

isa_t 是一个共用体,在64位机器中会启用NONPOINTER_ISA优化,具体见谈谈iOS的内存管理方式的理解,这时候isa就不单纯用于存储指向某个类的指针。那么当启用NONPOINTER_ISA优化的时候作用域是怎样的呢?我们这里看到****#if defined(ISA_BITFIELD)****,这个条件编译,ISA_BITFIELD是在isa.h中定义的我们来看下它的具体定义:

# if __arm64__
//.........
# 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

下面是这些字段的意义:

  • nonpointer (1位) 标记是否是纯的ISA指针,还是非指针型的NOPOINTER_ISA指针indexed,0表示普通的isa,1表示NOPOINTER_ISA指针

  • has_assoc (1位) 表示该对象是否包含 associated object,如果没有,则析构时会更快

  • has_cxx_dtor (1位) 表示该对象是否有 C++ 或 ARC 的析构函数,如果没有,则析构时更快

  • shiftcls (33位) 类的指针

  • magic(6位) 固定值为 0xd2,用于在调试时分辨对象是否未完成初始化

  • weakly_referenced(1位) 标记对象是否有弱引用指针weakly_referenced

  • deallocating (1位) 该对象是否正在析构

  • has_sidetable_rc (1位) 是否使用了引用计数表sideTable

  • extra_rc(19位) 存储引用计数值减一后的值 (首先会存储在该字段中,当到达上限后,has_sidetable_rc 等于1,对应的引用计数值存入相应的引用计数表中)

  • cache_t 结构

cache_t主要用于存储某个类中使用过方法的缓存,一般一个类会有很多方法,这些方法并不都是常用的,如果每次调用都需要从该类的所有方法列表中查询明显会降低效率,所以这里引入了cache_t,通过这种以空间换时间的方式来加快方法查找效率。

struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
//......
};

cache_t中包含三个属性:

_buckets 指向bucket_t结构体,它实际上是一个可动态扩展的哈希表。在存储的数据超过3/4的时候就会调用expend方法进行扩展。****_mask**** 表示整个_buckets链表的大小,****_occupied****表示当前_buckets链表里面缓存的bucket_t节点数。

struct bucket_t {
private:
MethodCacheIMP _imp;
cache_key_t _key;
}

bucket_t 结构也十分简单,就是一个以selector映射而成的cache_key_t以及方法指针_imp。

  • class_data_bits_t 结构

我们再继续看objc_class中的class_data_bits_t类型:

struct class_data_bits_t {

uintptr_t bits;

//.....
public:

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

//......
}

这里最关键的数据为class_rw_t,它才是包含类最有用信息的部分,我们从名字上可以看出它是可读写的,为什么要强调它是可读写的,因为它里面还有一个十分重要的class_ro_t类型。这个我们后面再介绍,我们先将重心放在class_rw_t中:

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;

public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}

//........
};

  • methods对象或者类的方法,这部分是可读写的,在Runtime阶段,会将分类的中的方法追加到这部分。
  • properties对象或者类的属性
  • protocols对象或者类的协议
  • ro对象或者类中只读的部分,这部分在编译时期就已经确定了。

我们再来看下class_ro_t结构:

struct class_ro_t {

const char * name;
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;

const ivar_list_t * ivars;
const uint8_t * ivarLayout;
const uint8_t * weakIvarLayout;

method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
property_list_t *baseProperties;

//......
};

它包括实例起始位置instanceStart,以及实例大小instanceSize
成员变量相关:ivarsivarLayoutweakIvarLayout
基础成分:baseMethodListbaseProtocolsbaseProperties

  • SEL 定义

SEL和id Class 一样都是objc对应类型的结构体指针的便捷定义。

typedef struct objc_selector *SEL;

objc_selector是一个映射到方法的C字符串,不同类中相同名字的方法所对应的selector是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的selector。由于这点特性,导致了OC不支持函数重载。简单说SEL不包含返回值类型,参数类型,以及所属类的信息。只包含方法名对应的信息,实际上它在Runtime中作为消息发送给对象,对象根据它来查找对应的 IMP进行执行。

  • IMP 定义

IMP是函数指针,即函数执行的入口。该函数使用标准的C调用。

typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 

它包含两个参数:

  • 第一个参数指向 self(它代表当前类实例的地址,如果是类则指向的是它的元类),作为消息的接受者;

  • 第二个参数代表方法的选择子;

  • … 代表可选参数,前面的 id 代表返回值。

  • Method 定义
typedef struct method_t *Method;
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
//.....
};

我们知道一个方法的组成包括方法返回值,方法参数,方法名,方法实现体,对应于method_t分别是typesnameimp,关于types涉及到类型编码,如果大家不了解可以通过如下链接进行扩展阅读:

在实际编程中可以通过method_getTypeEncoding方法来获得:

const char * _Nullable method_getTypeEncoding(Method _Nonnull m)

  • protocol_t 定义

protocol_t 主要用于存放协议相关的数据(注意它其实也是一个objc_object),由于协议也可以遵循协议,所以协议内部有协议列表用于存放当前协议所遵循的协议。而实例方法和类方法都包含必须实现和可选实现的部分,这两部是分开存放的,同时需要注意的是协议内部也为实例方法中提供了属性的存储,实际上协议也可以声明属性的,在介绍协议的使用的时候会给予介绍。

struct protocol_t : objc_object {

//....
struct protocol_list_t *protocols;
method_list_t *instanceMethods;
method_list_t *classMethods;
method_list_t *optionalInstanceMethods;
method_list_t *optionalClassMethods;
property_list_t *instanceProperties;
uint32_t size; // sizeof(protocol_t)
uint32_t flags;
//.......
};
  • property_t 定义
struct property_t {
const char *name;
const char *attributes;
};

对于属性结构比较简单就只有属性名字,以及对应的属性字符串,至于属性至于属性字符串的介绍可以看下面官方的文档介绍:

struct ivar_t {

int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;
//.....
};

如果我们使用下面方式声明实例变量,那么这些实例变量就会转换为ivar_t

@interface TestClass : NSObject {
@public
NSString *_name;
@private
NSString *_ID;
}

但是实际开发中大多数使用属性而不使用这种方法,使用这种写法,对象布局在编译器就已经固定了。只要碰到访问_name变量的代码,编译器就把其替换为偏移量ivar_t 中的(offset),这个偏移量是硬编码,表示该变量距离存放对象的内存区域的起始地址有多远。但是如果在运行过程中,又新增了一个实例变量,硬编码于其中的变量就会读到错误的值,这也是为什么OC无法动态添加成员变量的原因。

最后使用一个图来总结下全文的内容:

Contents
  1. 1. objc_object 结构
  2. 2. objc_class 结构
  3. 3. isa_t 结构
  4. 4. cache_t 结构
  5. 5. class_data_bits_t 结构
  6. 6. SEL 定义
  7. 7. IMP 定义
  8. 8. Method 定义
  9. 9. protocol_t 定义
  10. 10. property_t 定义
  11. 11. ivar_t 定义