参考源码位置:在 Apple Open Source
🔹 8连问+答 全览
| 问题 | 答案简述 |
|---|---|
| RunLoop 可以做什么? | 管理事件循环、保活线程、调度 Timer、处理异步事件 |
| 线程与 RunLoop关系 | 每线程一个 RunLoop,主线程自动启动,子线程需手动启动 |
| RunLoop组成 | Mode、Input Source、Timer Source、Observer |
| 如何启动 | 主线程自动,子线程需 run() |
| 启动条件 | 线程存在+至少一个事件源 |
| 状态 | Entry、BeforeTimers、BeforeSources、BeforeWaiting、AfterWaiting、Exit |
| 子线程需要保活吗 | 需要;主线程不需要 |
| 保活方式 | 启动RunLoop、死循环、GCD Timer、持续添加任务 |
1️⃣ RunLoop 可以做什么?
RunLoop 是线程的事件循环,用来管理和调度事件、定时器、输入源等。它的主要作用:
- 保持线程在有任务时运行、没任务时休眠,提高性能。
- 监听各种事件源(触摸事件、网络、Port、Timer等),并分发到对应的处理函数。
- 定时任务调度(
Timer)。 - 处理异步事件(例如
performSelector:onThread:)。 - 保持线程“保活”(不退出)。
简单理解:RunLoop 就像线程的“心脏”,不停地循环等待、接收、处理事件。
2️⃣ 线程跟 RunLoop 有什么关系?
- 每条线程都有一个唯一的 RunLoop(由系统创建,但只有在第一次访问时才会真正创建)。(
_CFRunLoopGet0方法中建议看看) - 主线程的 RunLoop 在 App 启动时由系统自动启动,用来处理 UI 事件。
- 子线程默认没有启动 RunLoop,如果需要事件驱动或保活,需要手动启动。
- RunLoop 是线程的辅助工具,不是线程本身;它依赖线程存在,线程结束时 RunLoop 也随之销毁。
3️⃣ RunLoop 的组成
RunLoop 主要由以下几个部分组成:
-
Mode(运行模式)
- 不同模式包含不同的事件源。
- 常见模式:
NSDefaultRunLoopMode:默认模式,处理普通事件。UITrackingRunLoopMode:处理 UI 滑动等高优先级事件。NSRunLoopCommonModes:公共模式组,包含多个模式。
-
Input Source(输入源)
- Port Source:基于内核的消息源(Mach Port)。
- Custom Source:自定义事件源。
-
Timer Source(定时器源)
- 基于时间的事件(
Timer)。
- 基于时间的事件(
-
Observer(观察者)
- 监听 RunLoop 状态变化(进入循环、处理事件、休眠、退出等)。
4️⃣ 如何启动 RunLoop?
- 主线程:系统自动启动,不需要手动启动。
- 子线程:需要手动调用
run()或CFRunLoopRun()。
示例:子线程启动 RunLoop
Thread {
print("子线程启动")
let runLoop = RunLoop.current
runLoop.add(NSMachPort(), forMode: .default)
runLoop.run() // 启动循环
}.start()
5️⃣ 启动 RunLoop 有哪些条件?
RunLoop 启动的前提:
- 线程必须存在。
- 必须有至少一个事件源(Input Source 或 Timer),否则启动后会立即退出。
- 调用
run()或CFRunLoopRun()开始循环。
示例:无事件源会立即退出
Thread {
RunLoop.current.run() // 没有事件源,RunLoop立刻退出
print("RunLoop退出")
}.start()
6️⃣ RunLoop 有几种状态?
RunLoop 内部状态(CFRunLoopActivity):
kCFRunLoopEntry:进入 RunLoop。kCFRunLoopBeforeTimers:即将处理 Timer。kCFRunLoopBeforeSources:即将处理 Source。kCFRunLoopBeforeWaiting:即将休眠。kCFRunLoopAfterWaiting:刚从休眠中唤醒。kCFRunLoopExit:退出 RunLoop。
示例:监听 RunLoop 状态
let observer = CFRunLoopObserverCreateWithHandler(nil, CFRunLoopActivity.allActivities.rawValue, true, 0) { _, activity in
print("RunLoop状态变化: \(activity)")
}
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, .defaultMode)
7️⃣ 子线程需要保活,主线程需要吗?为什么?
- 主线程不需要保活:因为系统在 App 启动时就启动了主线程的 RunLoop,并一直运行,负责 UI 事件处理。
- 子线程需要保活:子线程执行完任务后会退出,如果需要持续处理事件(如网络回调、定时器、Port通信),必须启动 RunLoop 保活。
8️⃣ 线程保活有几种方式?
常见方式:
- 启动 RunLoop
Thread {
RunLoop.current.add(NSMachPort(), forMode: .default)
RunLoop.current.run()
}.start()
- 使用
while(true)循环(不推荐,CPU占用高)
Thread {
while true {
Thread.sleep(forTimeInterval: 1)
}
}.start()
- 使用 GCD 的
dispatch_source保活
let queue = DispatchQueue(label: "keepAliveThread")
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now(), repeating: 2)
timer.setEventHandler {
print("定时任务执行")
}
timer.resume()
- 使用 NSOperationQueue + Operation 保活(任务持续添加)