96SEO 2026-05-07 17:47 1
这两天我又把 RunLoop 的相关文档和源码重新翻了一遍。这个话题在 iOS 开发圈子里其实一点dou不新,甚至Yi经算是老朋友了但有意思的是:hen多同学平时天天在和它打交道,却未必真的知道它在干什么。

说实话,现在的面试环境,大家dou懂,内卷得厉害。Ru果你去面初级岗位,这个问题基本上是跑不了的。想当年作者还是初级的时候,连 RunLoop 是什么dou不知道,每次遇到这种问题dou会菊花一紧,生怕回答的少了同时也怕回答的不够全面。hen多人第一次kan Mode,会觉得这名字有点抽象,甚至有点云里雾里。
别被“八股文”吓跑,RunLoop 其实hen朴素我们先别急着上源码,也别急着背那些枯燥的概念。先记住一句话:RunLoop 和线程是一一对应理解的。
你Ke以把它想象成线程的管家。一个程序Neng一直运行,就是基于 runloop。每个线程Ru果想继续运行,不被释放,就必须有一个 runloop 来不停地跑圈,以来处理线程里面的各种事件和消息。主线程默认是开启一个 runloop 的,而子线程通常需要你自己去决定是否启动它。
从概念上讲,一个 RunLoop 主要围绕四类东西运转:Source、Timer、Observer以及 Mode。它的本质,和下面这段伪代码几乎一模一样:
function loop {
while {
const event = getNextEvent;
if {
handle;
} else {
sleep;
}
}
}
这就是 RunLoop 的核心逻辑:一个“事件循环”模型。线程进入循环后反复执行“接收消息 -> 处理消息 -> 没消息就休眠 -> 被唤醒后继续处理”这一套流程。Apple 官方文档也明确说明,RunLoop 是一个 event processing loop;而从经典的 CFRunLoop 源码解析视角来kan,它也完全Ke以理解成线程内部长期运行的事件循环。
这个设计非常重要。否则主线程Ru果一直死循环轮询事件,手机发热和掉电会快得像开了涡轮;Ru果线程处理完一个任务就退出,那 App 也根本不可Neng持续响应事件。宇宙不会允许这种离谱工程存在太久。
它到底解决了什么问题?所以从结果上kan,RunLoop 解决的是两个核心问题:
线程保活: 让线程在没事干的时候睡觉,不占用 CPU,有事的时候立刻醒来干活。
事件调度: 把各种乱七八糟的事件统一管理,按顺序分发。
Mode:那个让 Timer 失效的“筛子”这就像你开了一个筛子。默认状态下线程处理一部分事件;当用户开始拖拽 ScrollView 时RunLoop Ke以切到另一个 mode,只处理和拖拽geng相关的输入,暂时忽略别的一些东西。Apple 也明确说明了mode 的作用是根据 source 来过滤事件,而不是根据事件类型本身来过滤。
Apple 文档里把 RunLoop Mode 描述为:一组要监听的 input sources、timers,以及要通知的 observers 的集合。每次 RunLoop 运行时只会在某个特定 mode 下处理对应的事件;不属于当前 mode 的 source/timer,不会在这一轮被处理。
这句话翻译成人话就是:RunLoop 当前这一轮,只kan哪一组事件。
常见的几个模式Ke以先记住:
DefaultMode平时没事干就在这个模式。
UITrackingRunLoopMode拖拽 ScrollView 时切到这个模式。
UIInitializationRunLoopMode启动时用的,用完就换。
这个问题几乎是 RunLoop 的必考题了。原因并不神秘:你创建出来的 Timer,大概率默认被加在了 DefaultMode 里;而当你拖拽 ScrollView 时主线程 RunLoop 会进入 tracking 相关的 mode,这时候默认 mode 下的 timer 就不会被处理。
Apple 文档明确说明,timer 和 source dou和特定 mode 绑定;不在当前 mode 里的对象,要等 RunLoop 以后切回支持它的 mode 才会触发。这也就合理的解释了为什么当我们拖动 textView 的时候 timer 方法不执行了这也是一种 NSTimer 不准的情况。
所以解决思路也就顺理成章了:把 Timer 加到 CommonModes。
NSTimer *timer = ;
addTimer:timer forMode:NSRunLoopCommonModes];
这里的 common 本质上不是一个真正的独立 mode,而是一个“公共模式集合”。把 timer 加进去以后它就Neng在多种 common mode 下dou被监控,自然也就不会在滚动时轻易“哑火”了。
Source0 与 Source1:事件从哪来?Ru果你平时kan的是 CFRunLoop 源码分析文章,那还会经常见到 Source0 和 Source1 这套说法。Ke以先粗暴理解成:
Apple 官方主要把 input source 分成两类:
Source0: 非基于 Port 的,也就是那些手动触发的事件,比如点击按钮、触摸屏幕。这种事件需要你主动调用 CFRunLoopSourceSignal 来唤醒 RunLoop。
Source1: 基于 Port 的,也就是通过内核发消息过来的,比如硬件中断、系统消息。这种是主动“唤醒” RunLoop 的。
触摸、手势、各种输入事件之所以Neng不断进入 App,被分发到 UIWindow、UIView、UIGestureRecognizer,背后同样离不开主线程 RunLoop 对事件源的处理。经典解析中也展示了系统事件如何通过 Source1 进入应用内部分发链路。
Observer:默默无闻的监工Observer 不产生事件,它负责观察 RunLoop 当前走到了哪一步。Apple 文档列出的典型观察时机包括:Entry、BeforeTimers、BeforeSources、BeforeWaiting、AfterWaiting、Exit。
这个东西hen关键,因为系统里hen多“顺便Zuo一下”的工作,恰恰就是挂在这些观察点上的。
经典分析里提到,主线程 RunLoop 上挂了和 autorelease pool 相关的 observer:进入 loop 时创建池,准备休眠时销毁旧池并重建,退出 loop 时再Zuo一次销毁。也就是说我们hen多主线程回调,其实天然就被 autorelease pool 包着。AutoreleasePool 在 RunLoop 的两次 sleep 之间对 AutoreleasePool 进行 pop 和 push,将这次 loop 产生的临时对象清理掉。
此外hen多 setNeedsLayoutsetNeedsDisplay 并不会让 UI 立刻重绘,而是先标记“需要geng新”,再等到 RunLoop 的某个合适时机统一提交。经典分析中把这部分和 BeforeWaiting / Exit 这些阶段关联了起来。
hen多同学在子线程里写个 Timer,结果发现根本不回调,本质原因通常不是 Timer 坏了而是线程的 RunLoop 根本没跑,或者刚跑起来就退出了。
Apple 文档里给出的说法是:每个线程dou有关联的 RunLoop 对象,主线程的 RunLoop 会由应用框架自动配置并运行,而二级线程是否运行 RunLoop,则取决于你自己。只有在你真的需要它的时候,才需要显式启动。
还有一个hen容易踩坑的点:RunLoop 里必须至少有一个输入源或者 timer,否则一启动就会立刻退出。 Apple 官方文档对此写得hen直白。
一个hen常见的“子线程保活”写法大概是这样:
- threadMain {
@autoreleasepool {
NSRunLoop *runLoop = ;
// 添加一个 Port,防止 RunLoop 因为没事干而直接退出
forMode:NSDefaultRunLoopMode];
// 启动 RunLoop
;
}
}
这个写法的核心不是 NSMachPort 本身有多神秘,而是:先往 RunLoop 里塞一个 source,避免它因为空空如也而直接退出,然后再让 RunLoop 跑起来。 Apple 官方文档也明确说明,secondary thread 的 RunLoop 在启动前必须至少附着一个 input source 或 timer,否则会立刻结束。
Apple 给出的建议其实hen实用:只有当子线程需要geng强交互性时才需要显式运行 RunLoop。 比如下面这些场景:
需要使用端口或其他自定义输入源来与其他线程通信。
需要在线程上执行定时器任务。
需要使用 performSelector... 系列方法。
Apple 官方文档明确说了:performSelector:onThread: 这一类调用,目标线程必须有一个 active run loop;performSelector:withObject:afterDelay: 也是在当前线程的下一次 run loop cycle 中调度执行。
所以理解 RunLoop,不只是为了背面试题,而是为了在遇到卡顿、定时器异常、线程保活、异步回调这些问题时不至于两眼一黑,开始对着代码Zuo法事。
我一直觉得,RunLoop 这个东西Zui容易被误解的地方,在于它听起来太“底层”了于是hen多人会下意识觉得:业务开发也用不上。但真相往往比较朴素,甚至有点滑稽:你不是用不上 RunLoop,而是你天天在被 RunLoop 影响。
hen多平时kan起来零碎的问题,比如 Timer 在滚动时失效、子线程任务不回调、performSelector 不执行、UI 为什么不是立刻刷新,本质上douNeng用 RunLoop 这套模型解释清楚。这些问题kan起来东一榔头西一棒子,实际上背后douNeng收敛到同一个东西:RunLoop。
Ru果你的线程只是Zuo一个明确的、一次性的耗时任务,比如图片解码、文件处理、纯计算,那干完退出往往geng合适,没必要强行塞一个 RunLoop 进去。别什么dou开火车,线程也会累。
所以 RunLoop 这东西,真的不是为了面试八股才学。它geng像是一把钥匙:平时你可Neng把它丢在抽屉里但一旦遇到线程、事件、时序、刷新相关的问题,它就会突然变得非常好用。
Apple 文档里把一次 RunLoop 的执行顺序列得hen清楚,大体Ke以压缩成下面这条主线:先通知 observer -> 处理 timer/source -> 没事就休眠 -> 被 timer、source、超时或显式唤醒后再继续处理。
这也是为什么你不Neng把 NSTimer 当成一把精确到毫秒的手术刀。它geng像一个“尽量按时提醒你”的闹钟,而不是原子钟。毕竟Ru果当前 RunLoop 正在处理一个耗时hen长的 block,那 Timer 也只Neng排队等着。
Zui后Ru果你kan过一些调用栈或者源码解析文章,会发现 RunLoop 的底层核心休眠/唤醒机制和 mach port 消息密切相关;这也是为什么它NengZuo到“没事就睡,有事马上醒”。这也不对。Apple 文档提到,Core Foundation 那套 API 通常是线程安全的;但 NSRunLoop 本身并不像底层 CFRunLoopRef 那么天然线程安全,Zui好只在拥有它的线程里修改它。
希望大家下次再面对这个问题时Neng自信地告诉面试官:RunLoop 循环的不仅仅是代码,geng是整个 App 的生命线。
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback