以下是 Objective-C 中 RunLoop 的高频面试问题清单,涵盖基础概念、原理机制、线程关系、应用场景及实战排查,适用于中高级 iOS 开发岗位面试:
🔹 一、基础概念类
Q1:RunLoop 是什么?它的核心作用是什么?
答:
RunLoop 是与线程绑定的事件处理循环机制,用于管理线程的生命周期。
- 有事件时:唤醒线程处理任务(如 UI 事件、Timer、网络回调)
- 无事件时:让线程进入休眠,节省 CPU 资源
本质是一个do-while循环,通过mach_msg等系统调用实现阻塞等待。
Q2:NSRunLoop 和 CFRunLoopRef 有什么区别?
答:
对比项 NSRunLoop CFRunLoopRef 语言 Objective-C(面向对象) C(Core Foundation) 线程安全 ❌ 不安全 ✅ 线程安全 可混用 可通过 [runloop getCFRunLoop]获取底层 CF 对象可直接用于 NSRunLoop 功能 封装了 CF 接口,更易用 提供底层控制能力
🔹 二、核心机制类(重点!)
Q3:RunLoop 内部是如何工作的?请描述其执行流程。
答(结合源码逻辑):
RunLoop 每次循环按以下顺序执行:
- 通知 Observer:即将进入 Loop(
kCFRunLoopEntry)- 处理 Timer:触发到期的
NSTimer- 处理 Source0:执行已标记为
Signaled的非 Port 事件(如 performSelector)- 检查 Source1:若有基于
mach_port的事件(如 UI 触摸),跳至第9步- 通知 Observer:即将休眠(
kCFRunLoopBeforeWaiting)- 休眠:通过
mach_msg等待唤醒(由 Timer/Source1/手动 WakeUp 触发)- 通知 Observer:从休眠唤醒(
kCFRunLoopAfterWaiting)- 处理唤醒事件(如 Source1 消息)
- 通知 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 在系统中有哪些实际应用?
答(结合源码和调试经验):
AutoreleasePool:
主线程 RunLoop 注册两个 Observer:
- Entry 时:
_objc_autoreleasePoolPush()- BeforeWaiting/Exit 时:
Pop + Push(刷新池)UI 刷新:
- CADisplayLink 依赖 RunLoop 的
BeforeWaiting阶段触发界面重绘定时器:
NSTimer需加入 RunLoop 才生效,受 Mode 影响事件响应:
- 触摸事件通过 Source1(mach_port)唤醒 RunLoop 处理
PerformSelector:
performSelector:afterDelay:本质是创建 Timer 加入 RunLoop
🔹 五、实战排查类
Q8:主线程卡顿如何用 RunLoop 分析?
答:
原理:RunLoop 每次循环处理事件的时间应 < 16.7ms(60fps)。
监控方案:
创建一个高频率 Timer(如 1ms)
记录 Timer 实际触发时间 vs 预期时间
若延迟 > 阈值(如 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 的创建/销毁时机。
💡 面试加分回答技巧
- 结合源码:提到
CFRunLoop.c中的_CFRunLoopRun函数 - 对比 Swift:指出 Swift 中 RunLoop 用法一致(
RunLoop.main) - 联系 GCD:说明 GCD 的
dispatch_source_t可替代 NSTimer(不受 Mode 影响) - 提防陷阱:强调
run方法会阻塞线程,而runUntilDate:可设置超时
📌 终极提示:当被问到“RunLoop 应用”时,务必提到 AutoreleasePool 的自动管理机制——这是苹果官方最经典的 RunLoop 应用案例!
掌握这些问题,你不仅能应对基础面试,还能在深度追问中展现架构思维。建议结合 LLDB 调试 RunLoop 或 手写常驻线程 Demo 进一步巩固理解。