文章目录
一、Runtime介绍
iOS的Runtime,通常称为Objective-C Runtime,是一个C语言库,包含了很多底层的纯C语言API。,它是Objective-C语言动态特性的基石。这个系统在程序运行时提供了一系列强大的功能,允许我们在应用运行过程中动态地操作类和对象,执行诸如检查和改变对象、交换方法实现、动态添加方法或属性等操作。
高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,机器语言也是计算机能够识别的唯一语言,但是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OC到C语言的过渡就是由runtime来实现的,这里采用了消息传递的机制,在程序运行之前,消息都没有与任何方法绑定起来。只有在真正运行的时候,才会根据函数的名字来,确定该调用的函数。然而我们使用OC进行面向对象开发,而C语言更多的是面向过程开发,这就需要将面向对象的类转变为面向过程的结构体。
二、Runtime消息传递
当你通过对象调用方法时,例如像这样[obj someMethod]
编译器会将其转换为一个消息发送的底层调用,通常是 objc_msgSend(obj, @selector(someMethod))
。这个函数接受两个主要参数:方法的调用者和方法选择器(也就是方法名)。
objc_msgSend
,其 “ 原型” ( prototype )如下:
void objc_msgsend(id self, SEL cmd, ...)
第一 个参数代表接收者也就是方法调用者,第二个参数代表方法选择器(SEL 是选择子的类型)也就是方法的名字,后续参数就是消息中的 那些参数,其顺序不变。
在进行具体的方法实现查找时:
- 首先,Runtime系统会通过obj的 isa 指针找到其所属的class
- 接着在这个类的方法列表(method list)中查找与选择器(someMethod)匹配的方法实现(IMP)。
- 如果在当前类中没有找到,Runtime会沿着类的继承链往它的 superclass 中查找,直到到达根类(通常为 NSObject)。
- 一旦找到someMethod这个函数,就去执行它的实现IMP 。
但一个class 往往只有 20% 的函数会被经常调用,可能占总调用次数的 80% 。每个消息都需要遍历一次objc_method_list 并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。这也就是objc_class 中另一个重要成员objc_cache 做的事情 - 再找到someMethod之后,把someMethod 的method_name 作为key ,method_imp作为value 给存起来。当再次收到someMethod消息的时候,可以直接在cache 里找到,避免去遍历objc_method_list。
因此Runtime的消息传递流程应该是:
- 首先,Runtime系统会通过obj的 isa 指针找到其所属的class
- 接着在这个类的缓存中查找与选择器匹配的方法实现
- 如果缓存中没找到接着在这个类的方法列表(method list)中查找与选择器(someMethod)匹配的方法实现(IMP)。
- 如果在当前类中没有找到,Runtime会沿着类的继承链往它的 superclass 中查找,也是先查缓存再查方法列表,直到到达根类(通常为 NSObject)。
- 一旦找到someMethod这个函数,就去执行它的实现IMP 。
从下面的源代码可以看到cache是存在objc_class 结构体中的。
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
}
三、实例对象、类对象、元类对象
下面是OC2.0中关于类和对象的定义
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;
}
其关系图如下图所示:
- 分析上面的源代码不难看出在OC2.0中每个对象都有一个isa_t类型的结构体(也就是平常所说的isa指针,其实它的本质是个结构体)。
- objc_class继承于objc_object。所以在objc_class中也会包含isa_t类型的结构体isa。所以OC中类其实也是一个对象。在objc_class中,除了isa之外,还有3个成员变量,一个是父类的指针,一个是方法缓存,最后一个数据域(存储了类中的详细信息包括方法列表)。
- objc_object被源码typedef成了id类型,这也就是我们平时遇到的id类型。这个结构体中就只包含了一个isa_t类型的结构体。
当一个对象的实例方法被调用的时候,会通过isa指针找到相应的类,接着进行一系列操作,如果是对象的类方法被调用该怎么办呢,这里就引入了元类(meta-class)的概念。meta-class它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。
当一个对象的实例方法被调用的时候,会通过isa指针找到相应的元类,在元类的缓存中查找与选择器匹配的方法实现,如果没有找到再到元类的数据域的方法列表中查找,如果还没找到则沿着继承链找元类的父类,直到根类(NSObject)。如果找到就去执行它的方法实现。
对象的实例方法调用时,通过对象的 isa 在类中获取方法的实现。
类对象的类方法调用时,通过类的 isa 在元类中获取方法的实现。
对象,类,元类对应关系的图如下图:
图中实线是父类指针,虚线是isa指针。
- Root class (class)其实就是NSObject,NSObject是没有超类的,所以Root
class(class)的superclass指向nil。 - 每个Class都有一个isa指针指向唯一的Meta class
- Root class(meta)的superclass指向Root class(class),也就是NSObject
- 每个Meta class的isa指针都指向Root class (meta)
类对象和元类在编译期产生是单例(只能有一个),实例对象是运行期产生的,可以有无数个
四、isa_t结构体的具体实现
前面提到isa指针的本质是个结构体,其源代码如下:
union isa_t
{
isa_t() {
}
isa_t(uintptr_t value) : bits(value) {
}
Class cls;
uintptr_t bits;
}
通过源码不难发现isa是一个union联合体。
- 有一个无参数的构造函数用来进行默认的初始化
- 有一个接受一个uintptr_t类型的值来初始化bits字段的构造函数,允许直接以整数形式初始化isa_t
- 有一个指向所属类的指针
- 有一个无符号整数用来进行底层的位操作,利用整数的每一位来编码额外信息
下面是objc_object的源码,里面包含了关于isa指针的一些操作:
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
void initIsa