
1. 什么是 Runtime?
我们都知道,将源代码转换为可执行的程序,通常要经过三个步骤:编译、链接、运行。不同的编译语言,在这三个步骤中所进行的操作又有些不同。
C 语言 作为一门静态类语言,在编译阶段就已经确定了所有变量的数据类型,同时也确定好了要调用的函数,以及函数的实现。
而 Objective-C 语言 是一门动态语言。在编译阶段并不知道变量的具体数据类型,也不知道所真正调用的哪个函数。只有在运行时间才检查变量的数据类型,同时在运行时才会根据函数名查找要调用的具体函数。这样在程序没运行的时候,我们并不知道调用一个方法具体会发生什么。
Objective-C 语言 把一些决定性的工作从编译阶段、链接阶段推迟到 运行时阶段 的机制,使得 Objective-C 变得更加灵活。我们甚至可以在程序运行的时候,动态的去修改一个方法的实现,这也为大为流行的『热更新』提供了可能性。
而实现 Objective-C 语言 运行时机制 的一切基础就是 Runtime。
Runtime 实际上是一个库,这个库使我们可以在程序运行时动态的创建对象、检查对象,修改类和对象的方法。
2. 消息机制的基本原理
Objective-C 语言 中,对象方法调用都是类似 [receiver selector]; 的形式,其本质就是让对象在运行时发送消息的过程。
我们来看看方法调用 [receiver selector]; 在『编译阶段』和『运行阶段』分别做了什么?
- 编译阶段:
[receiver selector];方法被编译器转换为:-
objc_msgSend(receiver,selector)(不带参数) -
objc_msgSend(recevier,selector,org1,org2,…)(带参数)
-
- 运行时阶段:消息接受者
recevier寻找对应的selector。- 通过
recevier的isa 指针找到recevier的Class(类); - 在
Class(类)的cache(方法缓存)的散列表中寻找对应的IMP(方法实现); - 如果在
cache(方法缓存)中没有找到对应的IMP(方法实现)的话,就继续在Class(类)的method list(方法列表)中找对应的selector,如果找到,填充到cache(方法缓存)中,并返回selector; - 如果在
Class(类)中没有找到这个selector,就继续在它的superClass(父类)中寻找; - 一旦找到对应的
selector,直接执行recevier对应selector方法实现的IMP(方法实现)。 - 若找不到对应的
selector,消息被转发或者临时向recevier添加这个selector对应的实现方法,否则就会发生崩溃。
- 通过
在上述过程中涉及了好几个新的概念:objc_msgSend、isa 指针、Class(类)、IMP(方法实现) 等,下面我们来具体讲解一下各个概念的含义。
3. Runtime 中的概念解析
3.1 objc_msgSend
所有 Objective-C 方法调用在编译时都会转化为对 C 函数 objc_msgSend 的调用。objc_msgSend(receiver,selector); 是 [receiver selector]; 对应的 C 函数。
3.2 Class(类)
在 objc/runtime.h 中,Class(类) 被定义为指向 objc_class 结构体 的指针,objc_class 结构体 的数据结构如下:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa; // objc_class 结构体的实例指针
#if !__OBJC2__
Class _Nullable super_class; // 指向父类的指针
const char * _Nonnull name; // 类的名字
long version; // 类的版本信息,默认为 0
long info; // 类的信息,供运行期使用的一些位标识
long instance_size; // 该类的实例变量大小;
struct objc_ivar_list * _Nullable ivars; // 该类的实例变量列表
struct objc_method_list * _Nullable * _Nullable methodLists; // 方法定义的列表
struct objc_cache * _Nonnull cache; // 方法缓存
struct objc_protocol_list * _Nullable protocols; // 遵守的协议列表
#endif
};
3.3 Object(对象)
接下来,我们再来看看 objc/objc.h 中关于 Object(对象) 的定义。
Object(对象)被定义为 objc_object 结构体,其数据结构如下:
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa; // objc_object 结构体的实例指针
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
这里的 id 被定义为一个指向 objc_object 结构体 的指针。从中可以看出 objc_object 结构体 只包含一个 Class 类型的 isa 指针。
换句话说,一个 Object(对象)唯一保存的就是它所属 Class(类) 的地址。当我们对一个对象,进行方法调用时,比如 [receiver selector];,它会通过 objc_object 结构体的 isa 指针 去找对应的 object_class 结构体,然后在 object_class 结构体 的 methodLists(方法列表) 中找到我们调用的方法,然后执行。
3.4 Meta Class(元类)
从上边我们看出,对象(objc_object 结构体) 的 isa 指针 指向的是对应的 类对象(object_class 结构体)。那么 类对象(object_class 结构体)的 isa 指针 又指向什么呢?
object_class 结构体 的 isa 指针 实际上指向的的是 类对象 自身的 Meta Class(元类)。
那么什么是 Meta Class(元类)?
Meta Class(元类) 就是一个类对象所属的 类。一个对象所属的类叫做 类对象,而一个类对象所属的类就叫做 元类。
在 2. 消息机制的基本原理 中我们讲解了 对象方法的调用过程,我们是通过对象的 isa 指针 找到 对应的 Class(类);然后在 Class(类) 的 method list(方法列表) 中找对应的 selector 。
而 类方法的调用过程 和对象方法调用差不多,流程如下:
- 通过类对象
isa 指针找到所属的Meta Class(元类); - 在
Meta Class(元类)的method list(方法列表)中找到对应的selector; - 执行对应的
selector。
下面看一个示例:
NSString *testString = [NSString stringWithFormat:@"%d,%s",3, "test"];
上边的示例中,stringWithFormat: 被发送给了 NSString 类,NSString 类 通过 isa 指针 找到 NSString 元类,然后在该元类的方法列表中找到对应的 stringWithFormat: 方法,然后执行该方法。
3.5 实例对象、类、元类之间的关系
上面,我们讲解了 实例对象(Object)、类(Class)、Meta Class(元类) 的基本概念,以及简单的指向关系。下面我们通过一张图来清晰地表示出这种关系。

我们先来看 isa 指针:
- 水平方向上,每一级中的
实例对象的isa 指针指向了对应的类对象,而类对象的isa 指针指向了对应的元类。而所有元类的isa 指针最终指向了NSObject 元类,因此NSObject 元类也被称为根元类。 - 垂直方向上,
元类的isa 指针和父类元类的isa 指针都指向了根元类。而根元类的isa 指针又指向了自己。
我们再来看 父类指针:
-
类对象的父类指针指向了父类的类对象,父类的类对象又指向了根类的类对象,根类的类对象最终指向了 nil。 -
元类的父类指针指向了父类对象的元类。父类对象的元类的父类指针指向了根类对象的元类,也就是根元类。而根元类的父亲指针指向了根类对象,最终指向了 nil。
3.6 Method(方法)
object_class 结构体 的 methodLists(方法列表)中存放的元素就是 Method(方法)。
先来看下 objc/runtime.h 中,表示 Method(方法) 的 objc_method 结构体 的数据结构:
/// An opaque type that represents a method in a class definition.
/// 代表类定义中一个方法的不透明类型
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name; // 方法名
char * _Nullable method_types; // 方法类型
IMP _Nonnull method_imp; // 方法实现
};
可以看到,objc_method 结构体 中包含了 method_name(方法名),method_types(方法类型) 和 method_imp(方法实现)。下面,我们来了解下这三个变量。
SEL method_name; // 方法名
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
SEL 是一个指向 objc_selector 结构体 的指针,但是在 runtime 相关头文件中并没有找到明确的定义。不过,通过测试我们可以得出: SEL 只是一个保存方法名的字符串。
SEL sel = @selector(viewDidLoad);
NSLog(@"%s", sel); // 输出:viewDidLoad
SEL sel1 = @selector(test);
NSLog(@"%s", sel1); // 输出:test
IMP method_imp; // 方法实现
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
IMP 的实质是一个函数指针,所指向的就是方法的实现。IMP用来找到函数地址,然后执行函数。
char * method_types; // 方法类型
方法类型 method_types 是个字符串,用来存储方法的参数类型和返回值类型。
4. Runtime 消息转发
在 2. 消息机制的基本原理 最后一步中我们提到:若找不到对应的 selector,消息被转发或者临时向 recevier 添加这个 selector 对应的实现方法,否则就会发生崩溃。

4.1 消息动态解析
Objective-C 运行时会调用 +resolveInstanceMethod: 或者 +resolveClassMethod:,让你有机会提供一个函数实现。前者在 对象方法未找到时 调用,后者在 类方法未找到时 调用。我们可以通过重写这两个方法,添加其他函数实现,并返回 YES, 那运行时系统就会重新启动一次消息发送的过程。
主要用的的方法如下:
// 类方法未找到时调起,可以在此添加方法实现
+ (BOOL)resolveClassMethod:(SEL)sel;
// 对象方法未找到时调起,可以在此添加方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel;
/**
* class_addMethod 向具有给定名称和实现的类中添加新方法
* @param cls 被添加方法的类
* @param name selector 方法名
* @param imp 实现方法的函数指针
* @param types imp 指向函数的返回值与参数类型
* @return 如果添加方法成功返回 YES,否则返回 NO
*/
BOOL class_addMethod(Class cls, SEL name, IMP imp,
const char * _Nullable types);
举个例子:
#import "ViewController.h"
#include "objc/runtime.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 执行 fun 函数
[self performSelector:@selector(fun)];
}
// 重写 resolveInstanceMethod: 添加对象方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(fun)) { // 如果是执行 fun 函数,就动态解析,指定新的 IMP
class_addMethod([self class], sel, (IMP)funMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void funMethod(id obj, SEL _cmd) {
NSLog(@"funMethod"); //新的 fun 函数
}
@end
从上边的例子中,我们可以看出,虽然我们没有实现 fun 方法,但是通过重写 resolveInstanceMethod: ,利用 class_addMethod 方法添加对象方法实现 funMethod 方法,并执行。从打印结果来看,成功调起了funMethod 方法。
4.2 消息接受者重定向
如果上一步中 +resolveInstanceMethod: 或者 +resolveClassMethod: 没有添加其他函数实现,运行时就会进行下一步:消息接受者重定向。
如果当前对象实现了 -forwardingTargetForSelector: 或者 +forwardingTargetForSelector: 方法,Runtime 就会调用这个方法,允许我们将消息的接受者转发给其他对象。
其中用到的方法:
// 重定向类方法的消息接收者,返回一个类或实例对象
+ (id)forwardingTargetForSelector:(SEL)aSelector;
// 重定向方法的消息接收者,返回一个类或实例对象
- (id)forwardingTargetForSelector:(SEL)aSelector;
举个例子:
#import "ViewController.h"
#include "objc/runtime.h"
@interface Person : NSObject
- (void)fun;
@end
@implementation Person
- (void)fun {
NSLog(@"fun");
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 执行 fun 方法
[self performSelector:@selector(fun)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES; // 为了进行下一步 消息接受者重定向
}
// 消息接受者重定向
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(fun)) {
return [[Person alloc] init];
// 返回 Person 对象,让 Person 对象接收这个消息
}
return [super forwardingTargetForSelector:aSelector];
}
可以看到,虽然当前 ViewController 没有实现 fun 方法,+resolveInstanceMethod: 也没有添加其他函数实现。但是我们通过 forwardingTargetForSelector 把当前 ViewController 的方法转发给了 Person 对象去执行了。打印结果也证明我们成功实现了转发。
我们通过 forwardingTargetForSelector 可以修改消息的接收者,该方法返回参数是一个对象,如果这个对象是不是 nil,也不是 self,系统会将运行的消息转发给这个对象执行。否则,继续进行下一步:消息重定向流程。
4.3 消息重定向
如果经过消息动态解析、消息接受者重定向,Runtime 系统还是找不到相应的方法实现而无法响应消息,Runtime 系统会利用 -methodSignatureForSelector: 或者 +methodSignatureForSelector: 方法获取函数的参数和返回值类型。
- 如果
methodSignatureForSelector:返回了一个NSMethodSignature对象(函数签名),Runtime 系统就会创建一个NSInvocation对象,并通过forwardInvocation:消息通知当前对象,给予此次消息发送最后一次寻找 IMP 的机会。 - 如果
methodSignatureForSelector:返回nil。则 Runtime 系统会发出doesNotRecognizeSelector:消息,程序也就崩溃了。
所以我们可以在 forwardInvocation: 方法中对消息进行转发。
用到的方法:
// 获取类方法函数的参数和返回值类型,返回签名
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 类方法消息重定向
+ (void)forwardInvocation:(NSInvocation *)anInvocation;
// 获取对象方法函数的参数和返回值类型,返回签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 对象方法消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation;
举个例子:
#import "ViewController.h"
#include "objc/runtime.h"
@interface Person : NSObject
- (void)fun;
@end
@implementation Person
- (void)fun {
NSLog(@"fun");
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 执行 fun 函数
[self performSelector:@selector(fun)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES; // 为了进行下一步 消息接受者重定向
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil; // 为了进行下一步 消息重定向
}
// 获取函数的参数和返回值类型,返回签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"fun"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector; // 从 anInvocation 中获取消息
Person *p = [[Person alloc] init];
if([p respondsToSelector:sel]) { // 判断 Person 对象方法是否可以响应 sel
[anInvocation invokeWithTarget:p]; // 若可以响应,则将消息转发给其他对象处理
} else {
[self doesNotRecognizeSelector:sel]; // 若仍然无法响应,则报错:找不到响应方法
}
}
@end
可以看到,我们在 -forwardInvocation: 方法里面让 Person 对象去执行了 fun 函数。
既然 -forwardingTargetForSelector: 和 -forwardInvocation: 都可以将消息转发给其他对象处理,那么两者的区别在哪?
区别就在于 -forwardingTargetForSelector: 只能将消息转发给一个对象。而 -forwardInvocation: 可以将消息转发给多个对象。
以上就是 Runtime 消息转发的整个流程。
结合之前讲的 2. 消息机制的基本原理,就构成了整个消息发送以及转发的流程。下面我们来总结一下整个流程。
5. 消息发送以及转发机制总结
调用 [receiver selector]; 后,进行的流程:
- 编译阶段:
[receiver selector];方法被编译器转换为:-
objc_msgSend(receiver,selector)(不带参数) -
objc_msgSend(recevier,selector,org1,org2,…)(带参数)
-
- 运行时阶段:消息接受者
recevier寻找对应的selector。- 通过
recevier的isa 指针找到recevier的class(类); - 在
Class(类)的cache(方法缓存)的散列表中寻找对应的IMP(方法实现); - 如果在
cache(方法缓存)中没有找到对应的IMP(方法实现)的话,就继续在Class(类)的method list(方法列表)中找对应的selector,如果找到,填充到cache(方法缓存)中,并返回selector; - 如果在
class(类)中没有找到这个selector,就继续在它的superclass(父类)中寻找; - 一旦找到对应的
selector,直接执行recevier对应selector方法实现的IMP(方法实现)。 - 若找不到对应的
selector,Runtime 系统进入消息转发机制。
- 通过
- 运行时消息转发阶段:
- 动态解析:通过重写
+resolveInstanceMethod:或者+resolveClassMethod:方法,利用class_addMethod方法添加其他函数实现; - 消息接受者重定向:如果上一步添加其他函数实现,可在当前对象中利用
forwardingTargetForSelector:方法将消息的接受者转发给其他对象; - 消息重定向:如果上一步没有返回值为
nil,则利用methodSignatureForSelector:方法获取函数的参数和返回值类型。- 如果
methodSignatureForSelector:返回了一个NSMethodSignature对象(函数签名),Runtime 系统就会创建一个NSInvocation对象,并通过forwardInvocation:消息通知当前对象,给予此次消息发送最后一次寻找 IMP 的机会。 - 如果
methodSignatureForSelector:返回nil。则 Runtime 系统会发出doesNotRecognizeSelector:消息,程序也就崩溃了。
- 如果
- 动态解析:通过重写
参考资料
- 文档:Objective-C 运行时(苹果官方文档)
- 文档:Objective-C 运行时编程指南(苹果官方文档)
- 博文:Runtime-iOS 运行时基础篇
- 博文:iOS Runtime 详解
- 博文:新手也看得懂的 iOS Runtime 教程
以上就是 iOS 开发:『Runtime』详解(一):基础知识 的所有内容了。
整篇文章主要就讲了一件事:消息发送以及转发机制的原理和流程。这也是 Runtime 系统的工作原理。
下一篇笔者准备讲一下『Runtime』的实战应用。
iOS 开发:『Runtime』详解 系列文章:
- iOS 开发:『Runtime』详解(一)基础知识
- iOS 开发:『Runtime』详解(二)Method Swizzling
- iOS 开发:『Runtime』详解(三)Category 底层原理
- iOS 开发:『Runtime』详解(四)获取类详细属性、方法
尚未完成:
- iOS 开发:『Runtime』详解(五)Crash 防护系统
- iOS 开发:『Runtime』详解(六)Objective-C 2.0 结构解析
- iOS 开发:『Runtime』详解(七)KVO 底层实现










