1. 什么是 RunLoop?
RunLoop(运行循环) 是 iOS/macOS 开发中用于管理线程事件和消息的核心机制。它通过一个循环不断监听和处理输入事件(如触摸、定时器、网络数据),并在没有任务时让线程休眠以节省资源。
- 本质 :基于 mach port 的事件循环机制。
- 核心作用:让线程在有任务时执行任务,无任务时休眠,避免资源浪费。
2. RunLoop 与线程的关系
- 主线程 :默认自动创建并启动 RunLoop(
UIApplicationMain
函数中启动)。 - 子线程:默认不开启 RunLoop,需手动创建和启动。
- 生命周期:线程与 RunLoop 一一对应,存储在全局字典中(线程销毁时 RunLoop 也释放)。
3. RunLoop 的核心组成
组件 | 作用 |
---|---|
Modes(模式) | 定义 RunLoop 在不同场景下监听的事件源(Source)和观察者(Observer)。 |
Sources(事件源) | - Source0 :处理 App 内部事件(如触摸、PerformSelector)。 - Source1:基于 mach port 的系统事件(如硬件事件)。 |
Timers(定时器) | 基于 RunLoop 的定时器(如 NSTimer ),受 RunLoop Mode 影响。 |
Observers(观察者) | 监听 RunLoop 的状态变化(如即将处理事件、即将休眠等),用于性能监控。 |
4. RunLoop 的 Mode(模式)
模式 | 描述 |
---|---|
Default(NSDefaultRunLoopMode ) |
默认模式,处理大多数事件(如普通触摸、网络请求)。 |
Tracking(UITrackingRunLoopMode ) |
界面滚动时的模式(如 UIScrollView 滑动),优先保证滑动流畅。 |
Common(NSRunLoopCommonModes ) |
一个虚拟模式,包含多个模式的集合(Default + Tracking),用于通用事件监听。 |
5. RunLoop 的典型应用场景
(1) 保持线程存活
通过 RunLoop 让子线程持续处理任务而不退出:
swift
class BackgroundThread {
private var thread: Thread?
private var shouldKeepRunning = true
func start() {
thread = Thread { [weak self] in
// 创建并启动 RunLoop
let runLoop = RunLoop.current
runLoop.add(NSMachPort(), forMode: .default)
while self?.shouldKeepRunning == true {
runLoop.run(mode: .default, before: .distantFuture)
}
}
thread?.start()
}
func stop() {
shouldKeepRunning = false
thread?.cancel()
}
}
(2) 优化滚动性能
将耗时任务放在 Default
模式,避免滚动时执行(如 UIScrollView
滑动):
swift
DispatchQueue.main.async {
// 在 Default 模式下执行任务
RunLoop.current.perform(inMode: .default) {
// 处理耗时任务(如数据解析)
}
}
(3) 监听 RunLoop 状态
通过 CFRunLoopObserver
监控主线程卡顿:
swift
let observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0) { observer, activity in
switch activity {
case .beforeWaiting:
print("RunLoop 即将休眠")
case .afterWaiting:
print("RunLoop 被唤醒")
default: break
}
}
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, .commonModes)
(4) 解决定时器失效问题
在滚动模式下(如 UIScrollView
滑动时),将 NSTimer
添加到 CommonModes
:
swift
let timer = Timer(timeInterval: 1.0, repeats: true) { _ in
print("Timer fired")
}
RunLoop.current.add(timer, forMode: .common)
6. RunLoop 的工作流程
- 接收事件:通过 mach port 接收系统事件(Source1)或 App 内部事件(Source0)。
- 通知 Observers :即将处理事件(
kCFRunLoopBeforeTimers
、kCFRunLoopBeforeSources
)。 - 处理事件:执行触发的 Source0、Source1 和 Timer 事件。
- 进入休眠 :若没有事件,调用
mach_msg
让线程休眠,等待新事件唤醒。 - 唤醒处理:被唤醒后处理新到达的事件,循环往复。
7. 注意事项
- 避免阻塞主线程:主线程 RunLoop 负责 UI 更新,长时间阻塞会导致卡顿。
- 子线程 RunLoop 管理:手动创建的 RunLoop 需自行控制生命周期(如添加保活 Source)。
- 性能优化:合理使用 Mode 隔离不同任务(如滑动时暂停后台任务)。
总结
场景 | RunLoop 的作用 | 关键技术点 |
---|---|---|
线程保活 | 让子线程持续处理任务 | 添加保活 Source(如 NSMachPort ) |
滑动性能优化 | 隔离耗时任务与滚动事件 | 使用 CommonModes 或模式切换 |
定时器管理 | 确保定时器在滚动时仍有效 | 注册到 CommonModes |
卡顿监控 | 监听主线程 RunLoop 状态变化 | CFRunLoopObserver |
RunLoop 是 iOS 系统高效管理线程和事件的核心机制,合理利用可优化性能、实现复杂任务调度。在大多数情况下,开发者无需直接操作 RunLoop,但理解其原理对解决卡顿、线程管理等问题至关重要。