概念
Runtime 是一套底层全部用C语言代码构成的API,封装了很多动态性相关的函数。Objective-C 是一门动态语言,允许很多操作推迟到程序运行时再进行。OC的动态特性就是由 Runtime 来支撑和实现的,平时编写的OC代码,底层都是转换成了Runtime API 进行调用。
区别于C语言这样的静态语言
C 语言是一门静态语言,也就是说,在编译时,编译器就已经完全决定了函数的调用地址(也就是哪个函数会被执行)。编译器通过代码中的函数名,直接将它与内存中的函数地址关联起来。函数调用是非常确定的,也就是所谓的"静态绑定"。
相比 C 语言,Objective-C 是动态语言,这意味着方法的调用不是在编译时决定的,而是在运行时决定的。在 Objective-C 中,函数调用不是像 C 语言那样直接绑定到具体函数地址,而是通过一种叫做消息传递的机制来完成。编译时并不知道具体调用的是哪个方法,直到程序运行时,才通过 Runtime 系统查找要调用的方法。
Runtime无非就是去解决如何在运行时期找到调用方法这样的问题
对于实例变量寻找有如下的思路:
instance -> class -> method -> SEL -> IMP -> 实现函数
实例对象中存放 isa 指针以及实例变量,isa 指针可以找到该实例对象所属的类对象,类中存放着实例方法列表,在这个方法中 SEL 作为 key,IMP 作为 value,在编译时期,根据方法名字会生成一个唯一的 Int 标识,这个标识就是 SEL ,IMP 其实就是函数指针指向了最终的函数实现。
整个 Runtime 的核心就是 objc_msgSend 函数,通过给类发送 SEL 以传递消息,找到匹配的 IMP 再获取最终的实现。
相关的数据结构
isa指针
isa 指针用来维护对象和类之间的关系,并确保对象和类能够通过 isa 指针找到对应的方法、实例变量、属性、协议等;
在arm64架构之前,isa 就是一个普通的指针,直接指向objc_class,存储着Class、Meta-Class对象的内存地址;
从 arm64 架构开始,对 isa 进行了优化,变成了一个共用体union结构,不仅仅承担指针的作用,还使用位域来存储更多的信息,例如引用计数。
类 Class
当一个对象的实例方法被调用时,会通过isa指针找到这个类,然后在该类中方法列表中查找。
//Class定义为指向 objc_class 类型的指针
typedef struct objc_class *Class;
//runtime对 objc_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
};
对象 Object
平时使用的所有类型都是 id 类型,id 类型对象对应到 Runtime中,就是objc_object结构体。
实例对象 Object 是我们对类对象 alloc 或者 new 操作时所创建的,在这个过程中会拷贝实例所属的类的成员变量,但不拷贝类定义的方法。
调用实例方法时,系统会根据实例的 isa 指针去类的方法列表及父类的方法列表中寻找与消息对应的 selector 指向的方法。
typedef struct objc_object *id;
// 对象结构体
struct objc_object {
Class _Nonnull isa; OBJC_ISA_AVAILABILITY;
};
由此,我们得出了一个结论,类对象和实例对象的查找机制是一样的:
- 对象的实例方法调用时,通过对象的 isa 在类中获取方法的实现。
- 类对象的类方法调用时,通过类的 isa 在元类中获取方法的实现。
类class与元类meta-class
元类就是类对象的 isa 指向的类,也可以说是类对象的类
在面向对象中,一切都是对象。实例是对象,类本身也是对象。既然类是对象,它就需要一个"类"来描述自己------那就是元类。
类是对象的模版,定义实例该有什么属性和方法元类是类的模版,定义类本身该有什么方法,例如类方法

class、meta-class底层结构都是objc_class结构体,objc_class继承自objc_object,所以它也有isa指针,所以它也是对象;
class中存储着实例方法、成员变量、属性、协议等信息meta-class中存储着类方法等信息
isa指针和superclass指针的指向(如上图);
由此可见:
实例对象的isa指向类对象,类对象的isa指向元类对象;元类对象的isa指向根元类对象,根元类对象的isa指向自己类对象与元类对象的superclass指向各自的父类,特别注意的一点是根元类对象的superclass指向根类对象。
基类的meta-class的superclass指向基类的class,决定了一个性质:
当我们调用一个类方法,会通过class的isa指针找到meta-class,在meta-class中查找有无该类方法,如果没有,再通过meta-class的superclass指针逐级查找父meta-class,一直找到基类的meta-class如果还没找到该类方法的话,就会去找基类的class中同名的实例方法的实现。
方法 Method
Method就是我们平时所说的函数,它表示的是能够独立完成一个功能的一段代码,Method通过 SEL 和 IMP 两个属性,实现了快速查询方法及实现。
typedef struct objc_method *Method;
struct objc_method {
SEL method_name; //方法名
char *method_types; //方法类型
IMP method_imp; //方法实现
};
SEL
SEL是方法选择器, 可以将其理解为方法的ID,是方法名字符串的唯一标识
typedef struct objc_selector *SEL;
struct objc_selector {
char *name;
char *types;
};
获取SEL方法:
1.OC中,使用@selector("方法名字符串")
2.OC中,使用NSSelectorFromString("方法名字符串")
3.Runtime方法,使用sel_registerName("方法名字符串")
- 一个类的方法列表中不能存在两个相同的SEL
- selector是不包含参数类型的,方法名相同,则SEL相同,编译器会报错
- 不同类中可以有存在两个相同的SEL
- 它们的SEL相同,但是IMP不同
- Runtime 动态派发可以实现"同名方法、不同对象 → 调用不同实现"。
这也是Objective-C 不支持函数重载的原因
IMP
IMP 可以理解为函数指针,指向了最终的实现
// 指向一个方法实现的指针
typedef id (*IMP)(id, SEL, ...);
#endif
消息发送
Objectove-C中所有方法的调用 / 类的生成都在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法,也可以替换某个类的方法为新的实现。
当一个对象的sender调用代码[receiver message],实际上就是 Runtime 在进行消息发送,这个方法会被编译器转化成:
// 第一个参数类型是发送者, 第二个参数类型是SEL
id objc_msgSend ( id _Nullable self, SEL op, ... );
在运行阶段 ,调用objc_msgSend函数,消息接受者receiver 去寻找对应的selector
objc_msgSend函数的调用流程:
- 检查这个
selector是否应该被忽略 - 检查发送的
target是否为nil,如果是则忽略该消息- 如果没有处理 nil 的函数,就自动清理并返回。
- 这一点就是为何在 Objective-C 中给 nil 发送消息不会崩溃的原因
- 通过
recevier的isa指针找到 recevier 的类Class - 在类
Class的方法缓存cache散列表中寻找对应的IMP(快查)- 在 cache 中没有找到对应的 IMP ,就继续在类 Class 的方法列表(慢查)
methodLists中寻找对应的 SEL ,如果找到,填充到 cache 中,返回 SEL - 如果在类 Class 中没有找到这个 SEl,那就在它的
superClass父类中寻找 - 一旦找到对应的 SEL,直接执行 SEL 方法实现的
IMP
- 在 cache 中没有找到对应的 IMP ,就继续在类 Class 的方法列表(慢查)
- 如果还没找到则进入消息转发过程,消息被转发或者临时向 recevier 添加这个 selector 对应的实现方法,否则就会发生崩溃
消息转发
首先会通过resolveInstanceMethod 得知方法是否为动态添加。
-
YES 则通过
class_addMethod动态添加方法,处理消息;NO则进行下一步 -
dynamic属性就与这个过程有关,当一个属性声明为dynamic时,就是告诉编译器,开发者一定会在运行时动态添加setter/getter的实现,编译时不用自动生成。//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)
消息接受者重定向
如果上一步中 +resolveInstanceMethod:或者 +resolveClassMethod: 没有添加其他函数实现,运行时就会进行下一步:消息接受者重定向。
如果当前对象实现了 -forwardingTargetForSelector:,Runtime 就会调用这个方法,允许将消息的接受者转发给其他对象,其主要方法如下:
//重定向类方法的消息接收者,返回一个类
- (id)forwardingTargetForSelector:(SEL)aSelector
//重定向实例方法的消息接受者,返回一个实例对象
- (id)forwardingTargetForSelector:(SEL)aSelector
通过forwardingTargetForSelector 可以修改消息的接收者,该方法返回参数是一个对象,如果这个对象是不是 nil,也不是 self,系统会将运行的消息转发给这个对象执行。否则,继续进行下一步:消息重定向。
消息重定向
如果经过消息动态解析、消息接受者重定向,Runtime 系统还是找不到相应的方法实现而无法响应消息,Runtime 系统会利用 -methodSignatureForSelector: 方法获取函数的参数和返回值类型。
其过程:
-
如果
-methodSignatureForSelector:返回了一个NSMethodSignature对象(函数签名),Runtime 系统就会创建一个NSInvocation对象- 并通过
-forwardInvocation:消息通知当前对象,给予此次消息发送最后一次寻找 IMP 的机会。
- 并通过
-
如果
-methodSignatureForSelector:返回了一个 nil ,则 Runtime 系统会发出-doesNotRecognizeSelector:消息,程序也就崩溃了。// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation;
// 获取函数的参数和返回值类型,返回签名
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector;
获取方法签名是为了能让 runtime 正确构造NSInvocation。只有构造出正确 invocation,才能安全、正确地把消息转发给其他对象。
-forwardingTargetForSelector和-forwardInvocation: 两个方法都可以转发消息,区别就在于:
-forwardingTargetForSelector: 只能将消息转发给一个对象;-forwardInvocation:可以将消息转发给多个对象。
消息转发示意图如下所示

Runtime的应用
- 动态方法交换 Method Swizzling
- 原理是:通过 Runtime 获取到方法实现的地址,进而动态交换两个方法的功能
- 类目扩展类能力
- Category 本质就是 runtime 在加载时向类的方法列表追加方法
- KVO / KVC
- 反射
- 获取类详细属性:属性列表、所有成员变量、所有方法、遵循的协议
Swift中的Runtime
Swift默认是静态、安全的语言,想使用OC的动态特性,可以用以下三个关键词:
@objc- 让Swift的类、方法、属性暴露给OC Runtime
- 方法生成selector
- 类的方法会进入OC得methodLists
- 允许参与OC Runtime消息转发
dynamic- 强制某方法使用动态派发,走
objc_msgSend
- 强制某方法使用动态派发,走
@objc dynamic- 开启完整的OC Runtimer能力
- 可以进行方法交换、被动态添加方法、参与消息转发机制、被KVO动态子类化等等,几乎等同于OC方法
Swift标准库中的反射机制Mirror
Mirror 是 Swift 提供的一套轻量级反射系统,用于在运行时查看对象的类型结构,包括:
- 对象的类型
- 属性名和属性值
- 子级元素
- 父类信息
- 自身的 displayStyle(结构体 / 类 / 元组 / 枚举等)
Mirror 只支持读取 (只读反射),不支持动态修改类或方法;它适用于调试、打印、序列化等场景;Mirror 的设计目标是安全、轻量,而不是动态强大。
struct Person {
var name: String
var age: Int
}
let p = Person(name: "Tom", age: 20)
let mirror = Mirror(reflecting: p)
for child in mirror.children {
print(child.label!, child.value)
}
//输出
name Tom
age 20
https://juejin.cn/post/7188344966580535333
https://juejin.cn/post/7063376892178464799
https://hit-alibaba.github.io/interview/iOS/ObjC-Basic/Runtime.html
https://blog.csdn.net/yueliangmua/article/details/134939537?spm=1001.2014.3001.5502
https://blog.csdn.net/ScheenDuan/article/details/142762110?spm=1001.2014.3001.5502