赞
踩
欢迎访问我的博客原文
Runtime 是指将数据类型的确定由编译时推迟到了运行时。它是一套底层的纯 C 语言 API,我们平时编写的 Objective-C 代码,最终都会转换成 runtime 的 C 语言代码。
不过,runtime API 的实现是用 C++ 开发的(源码中的实现文件都是 .mm
文件)。
为了更全面地理解 runtime 机制,我们结合最新的objc4 源码来进行解读。
我们知道 Objective-C 是面向对象开发的,而 C 语言则是面向过程开发,这就需要将面向对象的类转变成面向过程的结构体。
在 Objective-C 中,所有的消息传递中的“消息”都会被编译器转化为:
id objc_msgSend ( id self, SEL op, ... );
比如执行一个对象的方法:[obj foo];
,底层运行时会被编译器转化为:objc_msgSend(obj, @selector(foo));
。
那么方法内部的执行流程究竟是怎么样的呢?我先来了解一些概念。
Objective-C 对象是由 id
类型表示的,它本质上是一个指向 objc_object
结构体的指针。
typedef struct objc_object *id;
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
};
struct objc_object {
private:
isa_t isa;
// public & private method...
}
我们看到 objc_object
的结构体中只有一个对象,就是指向其类的 isa
指针。
当向一个对象发送消息时,runtime 会根据实例对象的 isa
指针找到其所属的类。
Objective-C 的类是由 Class
类型来表示的,它实际上是一个指向 objc_class
结构体的指针。
typedef struct objc_class *Class;
objc_class
结构体中定义了很多变量:
struct objc_class : objc_object {
// 指向类的指针(位于 objc_object)
// Class ISA;
// 指向父类的指针
Class superclass;
// 用于缓存指针和 vtable,加速方法的调用
cache_t cache; // formerly cache pointer and vtable
// 存储类的方法、属性、遵循的协议等信息的地方
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
// class_data_bits_t 结构体的方法,用于返回class_rw_t 指针()
class_rw_t *data() {
return bits.data();
}
// other methods...
}
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
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;
char *demangledName;
#if SUPPORT_INDEXED_ISA
uint32_t index;
#endif
// other methods
}
objc_class
继承自 objc_object
,因此它也拥有了 isa
指针。除此之外,它的结构体中还保存了指向父类的指针、缓存、实例变量列表、方法列表、遵守的协议等。
元类(metaclass)是类对象的类,它的结构体和 objc_class
是一样的。
由于所有的类自身也是一个对象,我们可以向这个对象发送消息,比如调用类方法。那么为了调用类方法,这个类的 isa
指针必须指向一个包含类方法的一个 objc_class
结构体。而类对象中只存储了实例方法,却没有类方法,这就引出了元类的概念,元类中保存了创建类对象以及类方法所需的所有信息。
为了更方便理解,举个例子:
- (void)eat; // 一个实例方法
+ (void)sleep; // 一个类方法
// 那么实例方法需要由类对象来调用:
[person eat];
// 而类方法需要由元类来调用:
[Person sleep];
假如 person
对象也能调用 sleep
方法,那我们就无法区分它调用的就究竟是 + (void)sleep;
还是 - (void)sleep;
。
类对象是元类的实例,类对象的 isa
指针指向了元类。
这个说法可能有点绕,借助这张经典的图来理解:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rJIMqnfn-15
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。