在 iOS 和 macOS 开发中,RunLoop 是维持应用持续运行的核心机制。理解 NSRunLoop 与 CFRunLoop 的关系,本质上是理解 Foundation 框架与 Core Foundation 框架之间的“桥接”设计。
1. 两者的关系:封装与被封装
NSRunLoop 是对 CFRunLoop 的面向对象封装。
- CFRunLoop (Core Foundation 层) :由纯 C 语言实现。它是 RunLoop 的底层核心,提供了绝大部分的功能实现。它是线程安全的。
- NSRunLoop (Foundation 层) :由 Objective-C 实现。它提供了一套更易用的面向对象 API。但要注意,它不是线程安全的。
2. 它们共享数据结构吗?
是的,它们操作的是同一个底层对象。
当你调用 [NSRunLoop currentRunLoop] 时,底层实际上是调用了 CFRunLoopGetCurrent()。
NSRunLoop内部持有一个CFRunLoopRef的实例。- 你可以通过
NSRunLoop的getCFRunLoop方法(Swift 中是getCFRunLoop())轻松地在这两者之间转换。
底层逻辑: 每一个线程都有且仅有一个与之对应的
CFRunLoop。无论是通过NSRunLoop还是CFRunLoop访问,最终指向的都是存储在全局字典(以线程为 Key)里的那个同一个CFRunLoop结构。
3. 性能差异在哪里?
在大多数业务开发场景下,两者的执行性能差异几乎可以忽略不计,因为 NSRunLoop 的方法调用开销相对于 RunLoop 内部的 mach_msg 睡眠和唤醒机制来说非常小。
然而,真正的“性能”差异体现在以下几个维度:
A. 线程安全性(核心差异)
- CFRunLoop:可以在多线程环境下安全地添加 Source、Timer 或 Observer。
- NSRunLoop:因为其非线程安全特性,官方建议只在当前 RunLoop 所属的线程中操作它。跨线程操作
NSRunLoop可能导致未定义的行为或数据竞态。
B. 功能覆盖度
- CFRunLoop 更加底层且功能完整。许多底层的控制(如
CFRunLoopObserver的精细回调、判断 RunLoop 是否已经停止等)只能通过CFRunLoop实现。 - NSRunLoop 为了易用性简化了一些功能。例如,
NSRunLoop没有提供直接添加 Observer 的方法。
C. 启动开销
NSRunLoop 的 API 往往会自动处理一些逻辑(如 Mode 的切换),这在带来方便的同时,也增加了一点点方法派发的开销。
4. 选型建议
| 场景 | 推荐使用 | 原因 |
|---|---|---|
| 通用 UI 逻辑 / Timer | NSRunLoop | API 友好,符合 OC/Swift 习惯。 |
| 底层性能监控 | CFRunLoop | 需要添加 Observer 来监控 RunLoop 状态。 |
| 跨线程控制 RunLoop | CFRunLoop | 线程安全,能更稳定地进行添加/移除操作。 |
| 精细控制运行模式 (Mode) | CFRunLoop | 支持自定义 Mode 及更复杂的 RunLoop 运行控制。 |
💡 一个关键的小细节
如果你正在使用 Swift,苹果更倾向于让你使用 RunLoop 类(即 NSRunLoop 的 Swift 版本),但在处理高性能、高精度的计时器或需要监控主线程卡顿时,依然需要通过 RunLoop.current.getCFRunLoop() 降级回 CFRunLoop 来操作。