RunLoop

5 阅读5分钟

以下是 Objective-C 中 RunLoop 的高频面试问题清单,涵盖基础概念、原理机制、线程关系、应用场景及实战排查,适用于中高级 iOS 开发岗位面试:


🔹 一、基础概念类

Q1:RunLoop 是什么?它的核心作用是什么?


RunLoop 是与线程绑定的事件处理循环机制,用于管理线程的生命周期。

  • 有事件时:唤醒线程处理任务(如 UI 事件、Timer、网络回调)
  • 无事件时:让线程进入休眠,节省 CPU 资源
    本质是一个 do-while 循环,通过 mach_msg 等系统调用实现阻塞等待。

Q2:NSRunLoop 和 CFRunLoopRef 有什么区别?

对比项NSRunLoopCFRunLoopRef
语言Objective-C(面向对象)C(Core Foundation)
线程安全❌ 不安全✅ 线程安全
可混用可通过 [runloop getCFRunLoop] 获取底层 CF 对象可直接用于 NSRunLoop
功能封装了 CF 接口,更易用提供底层控制能力

🔹 二、核心机制类(重点!)

Q3:RunLoop 内部是如何工作的?请描述其执行流程。

(结合源码逻辑):
RunLoop 每次循环按以下顺序执行:

  1. 通知 Observer:即将进入 Loop(kCFRunLoopEntry
  2. 处理 Timer:触发到期的 NSTimer
  3. 处理 Source0:执行已标记为 Signaled 的非 Port 事件(如 performSelector)
  4. 检查 Source1:若有基于 mach_port 的事件(如 UI 触摸),跳至第9步
  5. 通知 Observer:即将休眠(kCFRunLoopBeforeWaiting
  6. 休眠:通过 mach_msg 等待唤醒(由 Timer/Source1/手动 WakeUp 触发)
  7. 通知 Observer:从休眠唤醒(kCFRunLoopAfterWaiting
  8. 处理唤醒事件(如 Source1 消息)
  9. 通知 Observer:即将退出 Loop(kCFRunLoopExit

📌 关键点

  • Source0 需手动 Signal + WakeUp 才能触发
  • Source1 可主动唤醒 RunLoop(如触摸事件)

Q4:RunLoop 的 Mode 有什么作用?常见 Mode 有哪些?


Mode 作用:隔离不同类型的事件源(Source/Timer/Observer),避免相互干扰。
常见 Mode

  • kCFRunLoopDefaultMode:默认模式,主线程日常运行在此
  • UITrackingRunLoopMode:ScrollView 滑动时切换至此,保证滑动流畅
  • kCFRunLoopCommonModes伪模式,是 Default + Tracking 等 Mode 的集合

经典问题:为什么 ScrollView 滑动时 NSTimer 不触发?
→ 因 Timer 默认在 DefaultMode,滑动时 RunLoop 切换到 TrackingMode,Timer 被暂停。
解决方案:将 Timer 添加到 NSRunLoopCommonModes


🔹 三、线程与生命周期

Q5:RunLoop 和线程的关系是什么?

  • 一一对应:每个线程有且仅有一个 RunLoop(通过全局字典 __CFRunLoops 存储)
  • 懒加载:RunLoop 在首次获取时创建(如 [NSRunLoop currentRunLoop]
  • 主线程特殊:App 启动时自动创建并运行主 RunLoop
  • 子线程默认不运行:需手动调用 [runLoop run] 启动,否则线程执行完即销毁

Q6:如何让子线程常驻(保活)?为什么 AFNetworking 要这么做?


实现方式

// 创建子线程
NSThread *thread = [[NSThread alloc] initWithBlock:^{
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    // 必须添加至少一个 source/timer 防止 runLoop 退出
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runLoop run]; // 启动 runloop
}];
[thread start];

AFNetworking 原因

  • 网络请求回调需在固定线程执行(避免多线程竞争)
  • 通过常驻线程 + RunLoop 管理异步任务,提升性能和稳定性

🔹 四、应用场景类(必考!)

Q7:RunLoop 在系统中有哪些实际应用?

(结合源码和调试经验):

  1. AutoreleasePool

    • 主线程 RunLoop 注册两个 Observer:

      • Entry 时:_objc_autoreleasePoolPush()
      • BeforeWaiting/Exit 时:Pop + Push(刷新池)
  2. UI 刷新

    • CADisplayLink 依赖 RunLoop 的 BeforeWaiting 阶段触发界面重绘
  3. 定时器

    • NSTimer 需加入 RunLoop 才生效,受 Mode 影响
  4. 事件响应

    • 触摸事件通过 Source1(mach_port)唤醒 RunLoop 处理
  5. PerformSelector

    • performSelector:afterDelay: 本质是创建 Timer 加入 RunLoop

🔹 五、实战排查类

Q8:主线程卡顿如何用 RunLoop 分析?


原理:RunLoop 每次循环处理事件的时间应 < 16.7ms(60fps)。
监控方案

  1. 创建一个高频率 Timer(如 1ms)

  2. 记录 Timer 实际触发时间 vs 预期时间

  3. 若延迟 > 阈值(如 30ms),说明 RunLoop 被长任务阻塞
    工具

  • Xcode Instruments → Core Animation(检测掉帧)
  • 自定义 RunLoop 监控(参考 ANREye 开源库)

Q9:为什么 performSelector:afterDelay: 在子线程可能失效?

  • performSelector:afterDelay: 会创建一个 Timer 并加入 当前线程的 RunLoop
  • 若子线程未启动 RunLoop(未调用 [runLoop run]),Timer 无法触发
    解决方案
  • 手动启动子线程 RunLoop
  • 改用 GCD:dispatch_after(dispatch_get_main_queue(), ...)(推荐)

🔹 六、进阶原理题

Q10:Observer 如何监听 RunLoop 状态?有哪些状态?


通过 CFRunLoopAddObserver() 添加观察者,监听以下状态:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0),  // 即将进入 Loop
    kCFRunLoopBeforeTimers  = (1UL << 1),  // 即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2),  // 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5),  // 即将休眠
    kCFRunLoopAfterWaiting  = (1UL << 6),  // 刚从休眠唤醒
    kCFRunLoopExit          = (1UL << 7)   // 即将退出 Loop
};

典型应用:AutoreleasePool 的创建/销毁时机。


💡 面试加分回答技巧

  1. 结合源码:提到 CFRunLoop.c 中的 _CFRunLoopRun 函数
  2. 对比 Swift:指出 Swift 中 RunLoop 用法一致(RunLoop.main
  3. 联系 GCD:说明 GCD 的 dispatch_source_t 可替代 NSTimer(不受 Mode 影响)
  4. 提防陷阱:强调 run 方法会阻塞线程,而 runUntilDate: 可设置超时

📌 终极提示:当被问到“RunLoop 应用”时,务必提到 AutoreleasePool 的自动管理机制——这是苹果官方最经典的 RunLoop 应用案例!


掌握这些问题,你不仅能应对基础面试,还能在深度追问中展现架构思维。建议结合 LLDB 调试 RunLoop手写常驻线程 Demo 进一步巩固理解。