iOS: RunLoop入门

发布时间:2026/11/11 13:26:24

iOS: RunLoop入门 文章目录RunLoopRunLoop 和 线程的关系为什么 RunLoop 能让线程休眠RunLoop Mode (运行模式)Timer 在滚动的时候会失效NSRunLoopCommonModesRunLoop 的 SourceTimerObserverRunLoop 一次完整运行过程RunLoop 和 线程保活为什么必须要有 addPort没有 addPort 的情况有 addPort 的情况RunLoop 与 performSelector常见的几种形式RunLoop 与 GCD 区别RunLoopRunLoop 本质上是一个事件循环机制, 让线程在没有任务是休眠, 有事件时被唤醒并处理事件普通线程-(void)threadMain{NSLog(开始执行);// 执行完// 线程结束}如果希望线程一直存活while(YES){// 等待任务}这种方式会导致 CPU 空转。RunLoop 解决的就是没有事件 ↓ 线程进入休眠 ↓ 有事件到来 ↓ 唤醒线程 ↓ 处理事件 ↓ 继续等待所以可以把它理解成RunLoop 线程的“事件调度中心” 休眠/唤醒机制RunLooP作用保持线程 (尤其是主线程) 持续运行, 不退出处理 App 中的各种事件 (触摸, Timer, performSelector, GCD主队列任务, 界面刷新)节省CPU 资源: 有事做事, 无事休眠RunLoop 和 线程的关系一个线程对应一个RunLoop, 但不是说每个线程启动后都有一个已经运行的RunLoop,主线程:NSRunLoop*runLoop[NSRunLoop mainRunLoop];主线程的RunLoop会被系统自动启动子线程则不同:NSThread*thread[[NSThread alloc]initWithBlock:^{NSLog(子线程);[[NSRunLoop currentRunLoop]run];}];[thread start];如果不调用:[[NSRunLoop currentRunLoop] run];,那么子线程执行完任务之后就结束了主线程的 RunLoop 在App启动时自动创建并执行子线程默认没有RunLoop, 需要手动获取并启动 (否则线程执行完成任务就退出)为什么 RunLoop 能让线程休眠底层并不是简单的while(1)而是RunLoop ↓ 检查有没有事件 ↓ 有事件 → 处理 ↓ 没事件 → 让线程进入休眠 ↓ Mach Port / Source / Timer 等事件到来 ↓ 系统唤醒线程所以 RunLoop 的核心价值之一就是避免线程在没有任务的时候持续占用 CPU。RunLoop的核心组成: Mode, Source, Timer, ObserverRunLoop Mode (运行模式)RunLoop 并不是把所有的事件全部一起处理, 它通过Mode对事件进行分类ModeMode 名称说明kCFRunLoopDefaultMode / NSDefaultRunLoopMode默认模式App 平时运行在这里UITrackingRunLoopMode界面追踪模式UIScrollView 滑动时切换到这个模式kCFRunLoopCommonModes / NSRunLoopCommonModes伪模式 / 标记不是真实 Mode。默认包含 Default TrackingUIInitializationRunLoopModeApp 启动时的临时 Mode启动完成后不再使用GSEventReceiveRunLoopMode系统内部接收事件用一般碰不到常见的 Mode 有:NSDefaultRunLoopMode NSRunLoopCommonModes UITrackingRunLoopMode主线程的 RunLoop 是在 UIApplicationMain 内部自动创建并启动的 (最终调用CFRunLoop), 所以App可以一直运行Timer 在滚动的时候会失效例如:[NSTimer scheduledTimerWithTimeInterval:1target:selfselector:selector(test)userInfo:nil repeats:YES];默认Timer被加入NSDefaultRunLoopMode那么这时候有一个TableView, 在滑动TableView的时候, Timer就会失效这是因为在UITableView上滑动的时候:RunLoop ↓ 进入 UITrackingRunLoopMode ↓ Default Mode 暂时不处理 ↓ Timer 不触发那么如何解决这个问题呢?只需要将 Timer 加入到NSRunLoopCommonModes就可以解决这个问题NSTimer*timer[NSTimer timerWithTimeInterval:1target:selfselector:selector(test)userInfo:nil repeats:YES];[[NSRunLoop mainRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];NSRunLoopCommonModesNSRunLoopCommonModes不是一个独立运行的 Mode它可以理解为 Mode集合, 包含多个Common Mode当把 Timer 加入到NSRunLoopCommonModes, 就是相当于就把 Timer 添加到当前RunLoop标记为 Common 的那些 Mode 中RunLoop 的 SourceSource 是 RunLoop 的事件来源常见可以分为:Source 0需要手动唤醒/处理的事件源例如:performSelector这类机制可以和 Source 0 联系起来Source 1基于 Mach Port 等底层事件机制例如:触摸事件 IPC 系统事件可以粗略理解为Source 1 ↓ 底层事件到达 ↓ 唤醒 RunLoop ↓ 处理事件TimerNSTimer也是RunLoop 的一个输入源, 这里需要注意的是: NSTimer 本身不是一个独立线程例如:NSTimer*timer[NSTimer scheduledTimerWithTimeInterval:1target:selfselector:selector(sayHello)userInfo:nil repeats:YES];Timer 是注册到 RunLoop 中NSTimer ↓ RunLoop ↓ 时间到了 ↓ RunLoop 检查 ↓ 执行 selector如果 RunLoop 不运行, NSTimer 就不会正常触发-(void)viewDidLoad{[superviewDidLoad];// 1. 创建一个 Timer每隔 1 秒执行一次 tick 方法NSTimer*timer[NSTimer scheduledTimerWithTimeInterval:1.0target:selfselector:selector(tick)userInfo:nil repeats:YES];for(inti0;i5;i){sleep(1);// 休眠 1 秒模拟重度计算NSLog(正在计算... 第 %d 秒,i1);}}-(void)tick{NSLog(定时器触发);}打印:正在计算... 第 1 秒 正在计算... 第 2 秒 正在计算... 第 3 秒 正在计算... 第 4 秒 正在计算... 第 5 秒 定时器触发从上面的代码运行结果可以观察到, 定时器事件在for循环结束之前没有触发让我们看看 CPU 实际执行指令的顺序:// RunLoop 的伪代码 function CFRunLoopRun() { while (true) { // ← RunLoop 的 while 循环 // 1. 检查 Timer // 2. 检查 Source // 3. 如果没有事件休眠 // 4. 如果有事件执行它 if (有事件需要处理) { // 这里会调用你的代码 [target performSelector:action]; // ← 在这里调用了 viewDidLoad // 你的代码执行完后才继续下一轮循环 } } }ObserverObserver 用来监听 RunLoop 的状态变化例如:RunLoop 即将进入 ↓ 处理 Timer ↓ 处理 Source ↓ 即将休眠 ↓ 被唤醒 ↓ 退出 RunLoop可以通过CFRunLoopObserver监听常见状态:kCFRunLoopEntry kCFRunLoopBeforeTimers kCFRunLoopBeforeSources kCFRunLoopBeforeWaiting kCFRunLoopAfterWaiting kCFRunLoopExitRunLoop 一次完整运行过程RunLoop 开始 ↓ BeforeTimers ↓ BeforeSources ↓ 处理 Timer / Source ↓ 是否还有事件 ↙ ↘ 有 没有 ↓ ↓ 继续 BeforeWaiting ↓ 休眠 ↓ 事件到达 / Timer 到期 ↓ AfterWaiting ↓ 处理事件 ↓ 循环可以把它浓缩成进入 ↓ 处理事件 ↓ 没有事件 ↓ 休眠 ↓ 被唤醒 ↓ 处理事件 ↓ 循环RunLoop 和 线程保活比如创建一个常驻线程:NSThread*thread[[NSThread alloc]initWithBlock:^{[[NSRunLoop currentRunLoop]addPort:[NSPort port]forMode:NSDefaultRunLoopMode];[[NSRunLoop currentRunLoop]run];}];NSThread* thread [[NSThread alloc] initWithBlock:^{ ... }];作用: 创建一个新的子线程对象initWithBlock: 告诉这个线程; “当启动时候,执行 Block 里面的代码”此时,线程还没有启动, 知识准别好了[[NSRunLoop currentRunLoop]addPort:[NSPort port]forMode:NSDefaultRunLoopMode];[[NSRunLoop currentRunLoop]run];[NSRunLoop currentRunLoop]获取当前线程的 RunLoop因为这段代码写在 Block 里, 而 Block 会在子线程中执行, 所以这里拿到的是子线程的 RunLoop[NSPort port]创建一个 Port (端口)可以把它理解成一个虚拟的 “信箱“, 有了这个信箱, RunLoop 就会认为: “我有东西要监听,不能退出”add:forMode:把上面的信箱加入到 RunLoop 中作用: 给RunLoop添加一个永久有效的事件源, 让它有事可做, 不会空转退出[thread start]启动子线程这时, 子线程开始执行 Block 里的代码为什么必须要有addPort这是一个极其关键的问题。没有addPort的情况[[NSRunLoop currentRunLoop] run];RunLoop 启动后会检查自己有没有事件源比如 Timer、Port、Observer。如果什么都没有RunLoop 会认为“我没事可做没必要继续跑。”于是RunLoop 立即退出子线程执行完所有代码线程被销毁。有addPort的情况[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run];添加 Port 后RunLoop 发现“我有一个 Port 要监听我不能退出。”于是RunLoop 进入永久等待状态线程永不销毁。RunLoop 与 performSelectorperformSelector是NSObject提供的一系列方法允许你延迟执行或在指定线程上执行某个方法。[selfperformSelector:selector(test)onThread:thread withObject:nil waitUntilDone:NO];常见的几种形式方法作用[obj performSelector:selector(method)]在当前线程立即执行[obj performSelector:withObject:afterDelay:]延迟执行依赖 RunLoop[obj performSelector:onThread:withObject:waitUntilDone:]在指定线程执行[obj performSelectorOnMainThread:withObject:waitUntilDone:]在主线程执行这个机制和目标线程的RunLoop 有密切关系主线程 ↓ performSelector ↓ 目标线程 RunLoop ↓ 唤醒 ↓ 执行 test如果创建了一个子线程:NSThread*thread...但是如果没有正确启动它的 RunLoop:[[NSRunloop currentRunLoop]run];那么一些基于RunLoop的任务就有可能无法进行.RunLoop 与 GCD 区别GCD更偏向 任务调度例如:dispatch_async(dispatch_get_global_queue(0,0),^{// 后台任务});关注任务 - Queue - ThreadRunLoop 更偏向 事件循环关注:事件 - RunLoop - 处理 - 休眠 - 唤醒两者不是互相替代的关系 ,GCD 和 RunLoop 在iOS 系统中是协同工作的
延伸阅读

更多相关文章

2026/11/11 13:26:23

BERT模型原理与实践指南:从架构到应用

1. BERT模型概述BERT(Bidirectional Encoder Representations from Transformers)是2018年由Google提出的革命性自然语言处理模型。作为首个真正实现双向上下文理解的预训练语言模型,它彻底改变了NLP领域的技术格局。我在实际项目中多次使用B…

2026/11/4 3:08:30

豌豆肉末:CookLikeHOC 蒸菜系列的标准化豌豆类小碗蒸配方拆解

豌豆肉末:CookLikeHOC 蒸菜系列的标准化豌豆类小碗蒸配方拆解 【免费下载链接】CookLikeHOC 🥢像老乡鸡🐔那样做饭。已添加2026年发布的《老乡鸡菜品溯源报告 2.0中新出现的菜品。主要部分于2024年完工,非老乡鸡官方仓库。文字来自…

2026/11/11 13:24:25

8级电工证过期了怎么复审?在哪考最靠谱

8级电工证过期了怎么复审?在哪考最靠谱 证书过期了不知道怎么复审延期,这种焦虑你肯定经历过。8级电工证作为电工行业的“硬通货”,一旦过期不仅影响工作,还可能带来法律风险。现在你最想知道的不是“为什么考”,而是“怎么复审”“在哪考”更省事。别…

2026/11/11 13:24:25

高压电工证错题怎么处理 通过率揭秘全在这了

高压电工证错题怎么处理 通过率揭秘全在这了 之前考的证在外省能不能转过来?这几乎是每个准备考高压电工证的人最关心的问题。尤其是像郑州的劳务班组负责人,常常需要带着团队跨省施工,证照的可迁移性直接关系到施工资格和项目合规性。今天我们就来聊聊…

2026/11/11 13:24:25

罗家田低压电工证过期了不知道怎么复审延期?别慌,含金量还在

罗家田低压电工证过期了不知道怎么复审延期?别慌,含金量还在 证书过期了不知道怎么复审延期,这事儿你不是一个人。不少在漯河从事水利工程的电工师傅,手上拿着罗家田低压电工证,但因为工作忙、不了解政策,导致证书一不小心就过了有效期,影响了工作安排…

2026/11/11 13:24:25

蓝田低压电工证千万别踩坑,实操考试心里没底怕挂科怎么破?

蓝田低压电工证千万别踩坑,实操考试心里没底怕挂科怎么破? 实操考试心里没底怕挂科?蓝田低压电工证千万别踩坑,很多人因为没准备充分或者选择错误的培训机构,最终耽误了考试和工作安排。尤其对于驻马店的中小施工企业负责人来说,低压电工证不仅是上岗的…

2026/11/11 13:19:24

华东进网电工证含金量高不高?实操考试到底考什么?

华东进网电工证含金量高不高?实操考试到底考什么? 找电工相关工作,手里没有华东进网电工证,根本进不了门。现在各行各业对特种作业人员的要求越来越高,尤其是像电工这种高风险工种, 华东进网电工证…

2026/11/11 12:42:59

电工证丢失还能补吗?千万别踩坑,丢了证等于失业

电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。…

2026/11/11 0:02:26

考低压电工证通过率考试真题与备考技巧

低压电工证通过率高不高?学历不高也能考,值不值得考? 你是不是总听说电工证能帮你找高薪工作,但又担心自己学历不够,考不上?其实,低压电工证考试门槛并没有你想象中那么高,关键是方法对不对。这篇文章就告诉你, 考低压电工证通过率到底有多高 ,…

2026/11/11 0:02:26

低压电工证学时千万别踩坑,濮阳毕业生怎么避坑报名

低压电工证学时千万别踩坑,濮阳毕业生怎么避坑报名 你是不是也和我一样,刚毕业就听说低压电工证学时必须达标才能上岗?可一打听,发现报名机构五花八门,价格参差不齐, 不知道去哪报名怕被中介坑…

2026/11/10 20:08:15

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/10 10:29:37

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑