好的,我们来深入探讨一下 iOS 开发中两个核心且容易混淆的概念:**Runtime** 和 **RunLoop**。
可以把它们想象成 iOS 应用的两位幕后功臣:
* **Runtime**:是 Objective-C 语言的**灵魂**,它让 Objective-C 成为一门动态语言,负责代码在**运行时的行为**。
* **RunLoop**:是应用心脏的**起搏器**,它负责在**运行循环中**处理事件、管理线程休眠与唤醒,保证应用能持续响应而又不至于空转耗电。
下面我们分别详细说明。
一、Runtime(运行时)
Runtime 是一个用 C 和汇编编写的库,它为 Objective-C 语言提供了动态特性。它是一套底层的 API,我们平时编写的 Objective-C 代码,最终都会在运行时被转换为 Runtime 的 C 语言函数调用。
核心理解:Objective-C 是一门动态语言
这意味着很多工作(如方法调用、成员变量访问)不是在编译时决定的,而是在程序运行时决定的。这正是 Runtime 的功劳。
主要职责和特性:
- **消息传递(Message Passing)**
* 这是 Runtime 最核心的概念。在 Objective-C 中,`[object methodName]` 这样的方法调用,编译器会将其转换为 `objc_msgSend(object, @selector(methodName))` 函数。
* 这个函数会去 `object` 的类中查找 `methodName` 对应的实现(IMP),如果找不到,会沿着继承链向上查找。如果最终都找不到,就会进入**消息转发(Message Forwarding)** 机制,给你三次机会来补救这个"未找到方法"的崩溃。
* **消息转发流程**:
-
**动态方法解析**:`+resolveInstanceMethod:` 或 `+resolveClassMethod:`,可以在这里动态地为该方法添加一个实现。
-
**备用接收者**:`-forwardingTargetForSelector:`,可以在这里将消息转发给另一个能处理该消息的对象。
-
**完整消息转发**:`-methodSignatureForSelector:` 和 `-forwardInvocation:`,可以获取方法签名并封装成一个 `NSInvocation` 对象,你可以任意处理这个调用(修改参数、修改调用目标、甚至吞掉消息)。
-
**类与对象的结构**
* 类本身也是一个对象,称为**类对象**(Class Object)。
* 每个实例对象(Instance)的 `isa` 指针(在现在版本的 Runtime 中,isa 可能被优化,但概念不变)指向它的类对象。
* 类对象中存储着:
* **方法列表(Method List)**:存放实例方法。
* **属性列表(Property List)**
* **成员变量列表(Ivar List)**
* 它的 `isa` 指针指向**元类(Meta Class)**,元类中存储着**类方法**。
* 这种结构构成了一个面向对象的、可继承的完整体系。
- **方法交换(Method Swizzling)**
* 利用 Runtime 可以在运行时动态地交换两个方法的实现(IMP)。
* 这是 AOP(面向切面编程)的利器,常用于无侵入地添加日志、统计、全局 UI 配置等。
* **示例**:交换 `viewDidAppear:` 方法,在所有控制器出现时自动打点。
```objectivec
Method originalMethod = class_getInstanceMethod([UIViewController class], @selector(viewDidAppear:));
Method swizzledMethod = class_getInstanceMethod([UIViewController class], @selector(my_viewDidAppear:));
method_exchangeImplementations(originalMethod, swizzledMethod);
```
- **关联对象(Associated Objects)**
* 允许在运行时为任何一个对象关联额外的键值数据。这相当于给已有的类动态添加了"属性"。
* 常用于为系统类(如 `UIView`)添加自定义数据,而无需子类化。
```objectivec
// 设置关联对象
objc_setAssociatedObject(object, &key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 获取关联对象
id value = objc_getAssociatedObject(object, &key);
```
**总结 Runtime**:它赋予了 Objective-C 强大的动态能力,使得一些看似"黑魔法"的操作成为可能,是很多高级特性和框架(如 KVO、KVC、AOP)的基础。
二、RunLoop(运行循环)
RunLoop 是一个与线程相关的、用于管理事件/消息的循环机制。它提供了一个入口函数,线程执行这个函数后,会进入"事件处理 -> 休眠 -> 等待事件唤醒 -> 事件处理"的循环中,直到循环结束。
核心理解:事件驱动的基石
没有 RunLoop,线程执行完任务就会退出。有了 RunLoop,线程就能常驻内存,随时等待并处理外部事件(如触摸、网络数据到达、定时器触发)。
主要职责和特性:
- **保持线程存活**
* 主线程的 RunLoop 在应用启动时默认是开启的,所以主线程不会退出。
* 对于子线程,RunLoop 默认不启动。如果你想创建一个常驻线程(比如一个一直等待任务的网络线程),你需要手动获取并运行它的 RunLoop。
```objectivec
// 子线程常驻
- (void)run {
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
runLoop addPort:\[NSMachPort port\] forMode:NSDefaultRunLoopMode\]; // 添加一个 Port 源来保活 \[runLoop run\]; // 进入无限循环 } } \`\`\` 2. \*\*处理输入事件\*\* \* RunLoop 监听多种\*\*事件源(Input Sources)\*\*: \* \*\*Source0\*\*: 非基于 Port 的事件,通常是应用内部事件,需要手动唤醒 RunLoop,如 \`performSelector:onThread:\`。 \* \*\*Source1\*\*: 基于 Port 的事件,通过内核进行线程间通信,能主动唤醒 RunLoop,如系统事件、网络套接字数据。 \* \*\*Timers\*\*: 定时器源,如 \`NSTimer\`。\`NSTimer\` 之所以需要添加到 RunLoop 才能工作,就是因为它依赖于 RunLoop 的循环来检查是否到点。 \* \*\*Observers\*\*: 观察者,可以监听 RunLoop 本身的活动状态,比如"即将处理 Timer"、"即将进入休眠"、"即将退出"等。常用于性能监控(如卡顿检测)。 3. \*\*管理运行模式(Modes)\*\* \* RunLoop 必须在特定的\*\*模式(Mode)\*\* 下运行。一个 Mode 是多个事件源(Source、Timer、Observer)的集合。 \* 运行时,RunLoop 只会处理当前 Mode 下的源。 \* \*\*常见模式\*\*: \* \`NSDefaultRunLoopMode\`: 默认模式,空闲状态。 \* \`UITrackingRunLoopMode\`: 界面跟踪模式,用于 \`UIScrollView\` 及其子类的滑动时,保证界面滑动不受其他事件影响。 \* \`NSRunLoopCommonModes\`: 一个伪模式,标记为 "Common" 的模式的集合。将一个源添加到这个模式,意味着它会在所有标记为 Common 的模式下都被处理。 \* \*\*经典场景\*\*: 一个 \`NSTimer\` 被添加到 \`NSDefaultRunLoopMode\`,当你在滑动 \`UIScrollView\` 时,RunLoop 会切换到 \`UITrackingRunLoopMode\`,此时 Timer 就不会被触发,导致滑动时计时器"卡住"。解决方法就是将 Timer 添加到 \`NSRunLoopCommonModes\`。 4. \*\*节省CPU资源,提高性能\*\* \* RunLoop 的精妙之处在于它的\*\*休眠\*\*机制。当没有事件需要处理时,RunLoop 会让线程进入休眠状态(由内核接管,CPU 不再分配时间片),此时几乎不消耗 CPU 资源。 \* 当有事件到达时(如一个网络请求返回、一个定时器到点),由内核通知 RunLoop,RunLoop 随即被唤醒,然后处理该事件。 \*\*总结 RunLoop\*\*:它是 App 能够持续运行、流畅响应的关键。它通过巧妙的"事件-休眠"循环,高效地管理了线程的生命周期和任务调度。 --- ### 三、Runtime 与 RunLoop 的关系与区别 \| 特性 \| Runtime \| RunLoop \| \| :--- \| :--- \| :--- \| \| \*\*层级\*\* \| 语言级,Objective-C 的底层基础 \| 框架级,通常是 Cocoa/CocoaTouch 框架的一部分 \| \| \*\*核心职责\*\* \| 实现 Objective-C 的动态特性(消息传递、方法解析等) \| 管理线程的事件循环(事件处理、线程休眠与唤醒) \| \| \*\*影响范围\*\* \| 影响单个方法调用、对象创建等微观行为 \| 影响整个线程的任务调度和生命周期 \| \| \*\*关系\*\* \| \*\*RunLoop 本身是由 C 语言函数实现的,它的循环体中会调用到由 Runtime 管理的 Objective-C 方法。\*\* 例如,当一个 Source1 端口事件唤醒 RunLoop 后,最终可能会触发一个 \`objc_msgSend\` 来调用某个对象的处理方法。 \| ### 四、实际应用场景 \* \*\*Runtime 应用\*\*: \* \*\*Method Swizzling\*\*: 无侵入埋点、全局 UI 样式统一。 \* \*\*关联对象\*\*: 为分类添加"属性"。 \* \*\*字典转模型\*\*: 在 \`-setValue:forKey:\` 时,利用 Runtime 获取属性列表,进行类型检查或转换。 \* \*\*消息转发\*\*: 实现多继承、容错处理(防止 unrecognized selector 崩溃)。 \* \*\*RunLoop 应用\*\*: \* \*\*卡顿监控\*\*: 通过向主线程 RunLoop 添加 Observer,监听 \`kCFRunLoopBeforeSources\` 和 \`kCFRunLoopBeforeWaiting\` 状态之间的耗时,如果超时则认为发生了卡顿。 \* \*\*自动释放池时机\*\*: AppKit 和 UIKit 在主线程 RunLoop 的 \`kCFRunLoopBeforeWaiting\` 时销毁旧的自动释放池并创建新的。 \* \*\*保证 NSTimer 在滑动时正常工作\*\*: 将其添加到 \`NSRunLoopCommonModes\`。 \* \*\*异步线程保活\*\*: 创建常驻后台线程执行特定任务。 希望这个详细的解释能帮助你彻底理解 Runtime 和 RunLoop 这两个 iOS 开发的基石。