谈谈你对iOS的runtime和runloop的了解
好的,我们来深入探讨一下 iOS 开发中两个核心且容易混淆的概念:**Runtime** 和 **RunLoop**。
可以把它们想象成 iOS 应用的两位幕后功臣:
* **Runtime**:是 Objective-C 语言的**灵魂**,它让 Objective-C 成为一门动态语言,负责代码在**运行时的行为**。
* **RunLoop**:是应用心脏的**起搏器**,它负责在**运行循环中**处理事件、管理线程休眠与唤醒,保证应用能持续响应而又不至于空转耗电。
下面我们分别详细说明。
---
### 一、Runtime(运行时)
Runtime 是一个用 C 和汇编编写的库,它为 Objective-C 语言提供了动态特性。它是一套底层的 API,我们平时编写的 Objective-C 代码,最终都会在运行时被转换为 Runtime 的 C 语言函数调用。
#### 核心理解:Objective-C 是一门动态语言
这意味着很多工作(如方法调用、成员变量访问)不是在编译时决定的,而是在程序运行时决定的。这正是 Runtime 的功劳。
#### 主要职责和特性:
1. **消息传递(Message Passing)**
* 这是 Runtime 最核心的概念。在 Objective-C 中,`[object methodName]` 这样的方法调用,编译器会将其转换为 `objc_msgSend(object, @selector(methodName))` 函数。
* 这个函数会去 `object` 的类中查找 `methodName` 对应的实现(IMP),如果找不到,会沿着继承链向上查找。如果最终都找不到,就会进入**消息转发(Message Forwarding)** 机制,给你三次机会来补救这个“未找到方法”的崩溃。
* **消息转发流程**:
1. **动态方法解析**:`+resolveInstanceMethod:` 或 `+resolveClassMethod:`,可以在这里动态地为该方法添加一个实现。
2. **备用接收者**:`-forwardingTargetForSelector:`,可以在这里将消息转发给另一个能处理该消息的对象。
3. **完整消息转发**:`-methodSignatureForSelector:` 和 `-forwardInvocation:`,可以获取方法签名并封装成一个 `NSInvocation` 对象,你可以任意处理这个调用(修改参数、修改调用目标、甚至吞掉消息)。
2. **类与对象的结构**
* 类本身也是一个对象,称为**类对象**(Class Object)。
* 每个实例对象(Instance)的 `isa` 指针(在现在版本的 Runtime 中,isa 可能被优化,但概念不变)指向它的类对象。
* 类对象中存储着:
* **方法列表(Method List)**:存放实例方法。
* **属性列表(Property List)**
* **成员变量列表(Ivar List)**
* 它的 `isa` 指针指向**元类(Meta Class)**,元类中存储着**类方法**。
* 这种结构构成了一个面向对象的、可继承的完整体系。
3. **方法交换(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);
```
4. **关联对象(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,线程就能常驻内存,随时等待并处理外部事件(如触摸、网络数据到达、定时器触发)。
#### 主要职责和特性:
1. **保持线程存活**
* 主线程的 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 开发的基石。
