1 什么是 Runtime?
Objective-C 的动态性,决定了它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。这个运行时系统就是Runtime,它基本上是用C和汇编写的一套API,这个库使C语言有了面向对象的能力。
2 消息机制的基本原理
OC的方法调用都是类似[receiver selector]的形式,其实每次都是一个运行时消息发送过程。
编译阶段:
[receiver selector]方法被编译器转化:
1.objc_msgSend(receiver,selector)(不带参数)
2.objc_msgSend(recevier,selector,org1,org2,…)(带参数)运行时阶段:消息接收者
recever寻找对应的selector:
1.recever能找到对应的selector,直接执行接收receiver对象的selector方法。
2.recever找不到对应的selector,消息被转发或者临时向recever添加这个selector对应的实现内容,否则崩溃。
OC调用方法[receiver selector],编译阶段确定了要向哪个接收者发送message消息,但是接收者如何响应决定于运行时的判断。
Objective-C代码在编译和运行阶段会被转化为Runtime方式运行,Objective-C类、对象和方法等都对应了C中的结构体,我们来看一下它们是如何定义的。
3 Runtime中的数据结构
Runtime代码如何查看呢,我们可以通过下面的方式:
1.导入下面的头文件,然后Command +鼠标右键点击,即可进入Runtime的源码文件。
#import <objc/runtime.h>
2.我们也可以通过组合键 [Cmd + Shift + O ] ,搜索相应的文件进入。
下面我们就分析一下Objective-C代码在C中对应的结构。
3.1 objc_msgSend
上面也提到过,Objective-C方法调用在编译时都会转化为对应C函数的调用:objc_msgSend(receiver,selector)。
3.2 Object(实例)
objc/objc.h中,我们来看一下Object(对象),是如何定义的:
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
我们知道id是一种通用的对象类型,它可以指向属于任何类的对象。在这里id被定义为一个指向 objc_object结构体的指针。
而objc_object结构体只包含一个Class类型的isa 指针,也就是说,一个Object(对象)唯一保存的就是它所属Class(类) 的地址。下面我们看一下 Class是如何定义的。
3.3 Class(类)
在objc/objc.h中,可以看到Class是一个指向objc_class结构体的指针:
typedef struct objc_class *Class;
在objc/runtime.h中,是objc_class结构体的具体定义:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#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
} OBJC2_UNAVAILABLE;
我们可以看到objc_class结构体中定义了很多的成员变量:指向父类的指针、类的名字、版本、实例大小、实例变量列表、方法列表、缓存、遵守的协议列表等。这个结构体存放的数据称为元数据(metadata)。
我们还能注意到objc_class结构体中也有一个isa指针。这就说明了Class本身其实也是一个对象,因此我们称之为类对象,类对象在编译期产生,用于创建实例对象,是单例。
3.4 元类(Meta Class):
我们可以发现实例对象和类对象结构体中都拥有一个isa指针,实例对象的isa指针指向他所属的类(Class),那么类对象的isa指针指向哪儿里呢?
类对象的isa指针指向了元类,元类(Meta Class)是一个类对象的类。在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。
为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,元类中保存了创建类对象以及类方法所需的所有信息。
3.5 实例对象、类、元类之间的关系
上面,我们已经了解了 实例对象(Object)、类(Class)、Meta Class(元类) 的基本概念。
下面,我们通过一张图,来清晰的了解下它们之间的继承关系,以及isa的指向关系:

isa指针指向:
- 实例对象的
isa指针指向了对应的类对象,而类对象的isa指针指向了对应的元类。 - 所有元类的isa指针最终指向了
NSObject元类,因此NSObject元类也被称为根元类。根元类的isa指针又指向了自己。
super_class指针指向:
- 类对象的
super_class指针指向了父类对象,父类对象又指向了根类对象,根类对象最终指向了nil。 - 元类的
super_class指针指向了父元类。父元类又指向了根元类。而根元类的super_class指针指向了根类对象,最终指向了nil。
3.6 Method(方法)
object_class中methodLists(方法列表)存放的元素就是Method(方法)。
在objc/runtime.h中,看下定义:
/// 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 // 方法实现;
}
下面,我们来了解下它的三个成员变量。
1. SEL(方法名)
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
SEL是一个指向objc_selector结构体的指针,然而我们并不能在Runtime中找到它的结构体的详细定义。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。
2. 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用来找到函数地址,然后执行函数。
3. char * method_types (方法类型)
方法类型method_types是个字符串,用来存储方法的参数类型和返回值类型。
到这里,Method的结构就已经很清楚了,Method将SEL(方法名) 和IMP(函数指针)关联起来,当对一个对象发送消息时,会通过给出的SEL(方法名)去找到IMP(函数指针),然后执行。
3.7 类缓存(objc_cache)
objc_cache用于缓存最近使用的方法。一个类只有一部分方法是常用的,每次调用一个方法之后,这个方法就被缓存到objc_cache中,下次调用时runtime会先在objc_cache中查找,如果objc_cache中没有,才会去methodList中查找。相比直接在类的方法列表中遍历查找,效率更高。
4 深入理解Rutime消息发送
我们在分析了OC语言对应的底层C结构之后,现在可以进一步理解运行时的消息发送机制。先前讲到,OC调用方法被编译转化为如下的形式:
id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
最后一步中我们提到:若找不到对应的selector,消息被转发或者临时向recevier添加这个selector应的实现方法,否则就会发生崩溃。
当一个方法找不到的时候,Runtime提供了 消息动态解析、消息接受者重定向、消息重定向 三种方法来处理,这三种方法的调用关系如下图:

4.1 动态方法解析(Dynamic Method Resolution)
所谓动态解析,我们可以理解为通过cache和方法列表没有找到方法时,Runtime为我们提供一次动态添加方法实现的机会,主要用到的方法如下:
//OC方法:
//类方法未找到时调起,可于此添加类方法实现
+ (BOOL)resolveClassMethod:(SEL)sel
//实例方法未找到时调起,可于此添加实例方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
//Runtime方法:
/**
运行时方法:向指定类中添加特定方法实现的操作
@param cls 被添加方法的类
@param name selector方法名
@param imp 指向实现方法的函数指针
@param types imp函数实现的返回值与参数类型
@return 添加方法是否成功
*/
BOOL class_addMethod(Class _Nullable cls,
SEL _Nonnull name,
IMP _Nonnull imp,
const char * _Nullable types)
下面使用一个示例来说明动态解析:ViewController类中声明方法却未添加实现,我们通过Runtime动态方法解析的操作为其添加方法实现,具体代码如下:
#import "ViewController.h"
#import <objc/runtime.h>
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[self class] performSelector:@selector(run)];
[self performSelector:@selector(walk)];
}
//重写父类方法:处理类方法
+ (BOOL)resolveClassMethod:(SEL)sel {
if(sel == @selector(run)){
class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(ssl_run)), "v@");
return YES; //添加函数实现,返回YES
}
return [class_getSuperclass(self) resolveClassMethod:sel];
}
//重写父类方法:处理实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if(sel == @selector(walk)){
class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(ssl_walk)), "v@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
+ (void)ssl_run {
NSLog(@"%s ",__func__);
}
- (void)ssl_walk {
NSLog(@"%s ",__func__);
}
运行程序,代码没有崩溃,并打印如下结果:
+[ViewController ssl_run]
-[ViewController ssl_walk]
这里+resolveInstanceMethod:或者 +resolveClassMethod:无论是返回YES,还是返回NO,只要其中没有添加其他函数实现,Runtime都会进行下一步:消息接受者重定向。
4.2 消息接收者重定向
这一步会调用下面两个方法:
//重定向类方法的消息接收者,返回一个类
+ (id)forwardingTargetForSelector:(SEL)aSelector
//重定向实例方法的消息接受者,返回一个实例对象
- (id)forwardingTargetForSelector:(SEL)aSelector
如果当前对象实现了这两个方法,Runtime就会调用这两个方法,允许我们将消息的接受者转发给其他对象。
下面使用一个示例来说明消息接收者的重定向:
#import "ViewController.h"
#import <objc/runtime.h>
@interface Person : NSObject
@end
@implementation Person
+ (void)run {
NSLog(@"%s ",__func__);
}
- (void)walk {
NSLog(@"%s ",__func__);
}
@end
@interface ViewController ()
@property (nonatomic, strong) Person *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[self class] performSelector:@selector(run)];
[self performSelector:@selector(walk)];
}
//重定向类方法:返回一个类对象
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
return [Person class];
}
return [super forwardingTargetForSelector:aSelector];
}
//重定向实例方法:返回类的实例
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(walk)) {
return self.person;
}
return [super forwardingTargetForSelector:aSelector];
}
- (Person *)person {
if (!_person) {
_person = [Person new];
}
return _person;
}
@end
代码没有崩溃,并打印如下结果:
+[ViewController ssl_run]
-[ViewController ssl_walk]
动态方法解析阶段无效时,我们可以通过forwardingTargetForSelector修改消息的接收者,该方法返回参数是一个对象,如果这个对象是非nil,非self,系统会将运行的消息转发给这个对象执行。否则,进行下一步:消息重定向。
4.3 消息重定向
这一步中首先会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil ,Runtime则会发出-doesNotRecognizeSelector: 消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送 -forwardInvocation:消息给目标对象。
看下方法的定义:
// 获取类方法函数的参数和返回值类型,返回签名
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 类方法消息重定向
+ (void)forwardInvocation:(NSInvocation *)anInvocation;
// 获取对象方法函数的参数和返回值类型,返回签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 对象方法消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation;
举个例子:
#import "ViewController.h"
#import <objc/runtime.h>
@interface Person : NSObject
@end
@implementation Person
- (void)walk {
NSLog(@"%s ",__func__);
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 执行walk函数
[self performSelector:@selector(walk)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES;// 返回YES,进入下一步转发
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil;// 返回nil,进入下一步转发
}
// 获取函数的参数和返回值类型,返回签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"walk"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];// 签名,进入forwardInvocation
}
return [super methodSignatureForSelector:aSelector];
}
// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;// 从anInvocation中获取消息
Person *p = [Person new];
if([p respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p];// 将消息转发给其他对象处理
}
else {
[self doesNotRecognizeSelector:sel];// 报错,代码崩溃
}
}
@end
代码没有崩溃,并打印如下结果:
-[Person walk]
这一步中,通过签名,Runtime生成了一个对象anInvocation,发送给了forwardInvocation,我们在forwardInvocation方法里面让Person对象去执行了walk函数。
以上就是Runtime的三次转发流程。
5 消息发送流程总结
调用[receiver selector]后,进行的流程:
-
编译阶段:
[receiver selector]方法被编译器转化:-
objc_msgSend(receiver,selector)(不带参数)。 -
objc_msgSend(recevier,selector,org1,org2,…)(带参数)。
-
-
运行时阶段:
recevier寻找对应的selector:- 通过
recevier的isa指针找到recevier的class(类)。 - 在
Class(类)的cache(方法缓存)中寻找对应的selector。 - 如果在
cache(方法缓存)中没有找到对应的selector,就继续在Class(类)的methodList(方法列表)中查找,如果找到,缓存到cache中,并返回selector。 - 如果在
class(类)中没有找到这个selector,就继续在它的superclass(父类)中寻找。 - 一旦找到
selector,直接执行相关联的IMP(方法实现)。 - 若找不到对应的
selector,Runtime系统进入消息转发阶段。
- 通过
-
消息转发阶段:
- 动态解析:通过重写
+resolveInstanceMethod:或者+resolveClassMethod:方法,利用class_addMethod方法添加其他函数实现。 - 消息接受者重定向:如果上一步没有添加其他函数实现,可在当前对象中利用
forwardingTargetForSelector:方法将消息的接受者转发给其他对象。 - 消息重定向:如果上一步返回值为
nil,则利用methodSignatureForSelector:方法获取函数的参数和返回值类型。- 如果
methodSignatureForSelector:返回nil。则Runtime系统会发出doesNotRecognizeSelector:消息,程序也就崩溃了。 - 如果
methodSignatureForSelector:返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。
- 如果
- 动态解析:通过重写
参考资料
- 文档:Objective-C 运行时(苹果官方文档)
- 文档:Objective-C 运行时编程指南(苹果官方文档)
- 博文:Runtime-iOS 运行时基础篇
- 博文:iOS Runtime 详解
- 博文:iOS 开发:『Runtime』详解(一)基础知识









