跳到主要内容

RunLoop

问题

RunLoop 是什么?它和线程的关系?有哪些实际应用?

答案

什么是 RunLoop

RunLoop 是一个事件处理循环——线程的 RunLoop 不断接收输入源(定时器、端口等),分发事件给处理者。没有 RunLoop 的线程执行完任务就退出。

RunLoop 与线程

  • 主线程 RunLoop 自动启动(保持 App 运行)
  • 子线程 RunLoop 默认不启动,需要手动 RunLoop.current.run()
  • 一个线程对应一个 RunLoop(懒创建)

RunLoop Mode

Mode说明
default默认模式,处理大多数事件
UITracking滑动 ScrollView 时进入
commondefault + UITracking 的集合
// Timer 在 default mode 注册 → 滑动时失效
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
print("tick")
}

// ✅ 加入 common mode → 滑动时也能触发
RunLoop.current.add(timer, forMode: .common)

实际应用

1. 保活子线程

class PermanentThread {
private var thread: Thread?

func start() {
thread = Thread {
// 添加一个空 Port 保持 RunLoop 不退出
RunLoop.current.add(NSMachPort(), forMode: .default)
RunLoop.current.run()
}
thread?.start()
}

func execute(_ task: @escaping () -> Void) {
perform(#selector(runTask(_:)), on: thread!, with: task, waitUntilDone: false)
}
}

2. 监控卡顿

// 通过 RunLoop Observer 检测主线程耗时
let observer = CFRunLoopObserverCreateWithHandler(nil, CFRunLoopActivity.allActivities.rawValue, true, 0) { _, activity in
switch activity {
case .beforeSources:
// 记录开始时间
break
case .afterWaiting:
// 计算耗时,超过阈值报告卡顿
break
default: break
}
}
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, CFRunLoopMode.commonModes)

常见面试问题

Q1: 为什么 Timer 在滑动时不触发?

答案:滑动 ScrollView 时,主线程 RunLoop 从 defaultMode 切换到 UITrackingRunLoopMode。如果 Timer 只注册在 defaultMode 下,滑动时不会被触发。解决方案:将 Timer 加入 .common modes。

Q2: RunLoop 内部是怎么休眠的?

答案:RunLoop 通过 mach_msg() 系统调用进入内核态休眠,不占用 CPU。当有事件到达(Timer 到期、端口消息等)时,内核唤醒线程,RunLoop 继续处理事件。这是"事件驱动"的核心——无事件时不消耗资源。

相关链接