iOS 中的 RunLoop 详解

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 的工作流程

  1. 接收事件:通过 mach port 接收系统事件(Source1)或 App 内部事件(Source0)。
  2. 通知 Observers :即将处理事件(kCFRunLoopBeforeTimerskCFRunLoopBeforeSources)。
  3. 处理事件:执行触发的 Source0、Source1 和 Timer 事件。
  4. 进入休眠 :若没有事件,调用 mach_msg 让线程休眠,等待新事件唤醒。
  5. 唤醒处理:被唤醒后处理新到达的事件,循环往复。

7. 注意事项

  • 避免阻塞主线程:主线程 RunLoop 负责 UI 更新,长时间阻塞会导致卡顿。
  • 子线程 RunLoop 管理:手动创建的 RunLoop 需自行控制生命周期(如添加保活 Source)。
  • 性能优化:合理使用 Mode 隔离不同任务(如滑动时暂停后台任务)。

总结

场景 RunLoop 的作用 关键技术点
线程保活 让子线程持续处理任务 添加保活 Source(如 NSMachPort
滑动性能优化 隔离耗时任务与滚动事件 使用 CommonModes 或模式切换
定时器管理 确保定时器在滚动时仍有效 注册到 CommonModes
卡顿监控 监听主线程 RunLoop 状态变化 CFRunLoopObserver

RunLoop 是 iOS 系统高效管理线程和事件的核心机制,合理利用可优化性能、实现复杂任务调度。在大多数情况下,开发者无需直接操作 RunLoop,但理解其原理对解决卡顿、线程管理等问题至关重要。

相关推荐
余道各努力,千里自同风5 分钟前
CSS“多列布局”
前端·css·html
Keya11 分钟前
使用 tinypng 脚本打包为exe 进行压缩图片
前端·python·程序员
wordbaby17 分钟前
React Router 的 handle 和 useMatches 的作用、场景和联系
前端·react.js
我的div丢了肿么办17 分钟前
ResizeObserver和IntersectionObserver的详细讲解
前端·javascript·vue.js
凌览18 分钟前
斩获 7k Star,这个桌宠项目火了🔥
前端·javascript·后端
opbr19 分钟前
🔥 扔掉虚拟DOM!百行代码实现高性能Vue内核,直接操作DOM竟这么香?
前端
Struggler28124 分钟前
cloudflare r2进行cdn加速
前端
snakeshe101027 分钟前
1. diff-更新 children
前端
啪叽32 分钟前
用纯CSS实现苹果iOS 26的Liquid Glass液体玻璃效果
前端·css·apple
一只大加号32 分钟前
qiankun基座+react+vite子应用搭建微前端
前端