iOS: RunLoop入门

发布时间:2026/9/12 21:36:05

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/9/12 21:36:05

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

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

2026/9/12 21:36:05

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

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

2026/9/12 22:46:11

脏纸编码与THP预编码:MU-MIMO非线性预编码的Matlab实现与仿真

简介:面向无线通信与信号处理方向的科研人员、研究生及高年级本科生,这份基于MATLAB的编码仿真资料聚焦脏纸编码(DPC)与Tomlinson-Harashima预编码(THP)的场景化实现。资源包含2个.m脚本,压缩包…

2026/9/12 22:46:11

霍夫圆变换实战:Python+OpenCV虹膜内外圆检测

简介:面向虹膜图像内外圆检测场景,这份资源是一套基于Python与OpenCV的完整实现,适合计算机视觉初学者、生物识别方向学生及需要快速上手霍夫圆变换的开发者。压缩包共9个文件,包含1个可直接运行的Python脚本和8张JPG图像&#xf…

2026/9/12 22:46:11

DE优化BP神经网络:Matlab实现与参数调优

简介:面向Matlab开发者和机器学习初学者的DE-BP差分算法优化BP神经网络分类预测资源,专注解决分类预测中传统BP网络易陷入局部最优、参数难调的问题。整个资源包共11个文件,包含5个Matlab脚本和4个mat数据集,脚本覆盖差分进化算法…

2026/9/12 22:46:11

全平台免费抓包工具盘点:从原理到实操一次讲透

做接口调试这些年,我几乎每天都要打开抓包工具看看请求到底长什么样。经常有朋友问我:免费的抓包工具到底选哪个?网上搜出来的答案七零八落,不是只讲Wireshark,就是只讲Fiddler,看完还是不知道怎么下手。正…

2026/9/12 22:46:11

清标慧审实测:28家标书人工与工具对比,316条漏检背后的真相

2026年1月初,我接到东部新区市政道路及配套管网工程的清标任务。一标段招标控制价9.76亿元,28家投标人,每家标书连报价带技术标平均300页出头,全部加起来将近8500页。甲方给了5个工作日,要一份能支撑评标委员会使用的清…

2026/9/12 2:05:33

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/12 3:55:12

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/12 10:09:03

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/12 0:04:17

MATLAB仿生优化框架:长鼻浣熊算法多策略融合实现

简介:本资源是一份面向智能优化算法研究者与MATLAB初学者的仿生智能算法实践代码包,聚焦于长鼻浣熊优化算法(COA)的多策略改进与性能验证。针对传统COA易陷局部最优、收敛精度不足等问题,作者融合Circle映射初始化提升…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 JavaWeb 的校园一卡通管理系统的设计与实现 基于 JavaWeb 的校园卡业务管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 Java 的图书馆借阅管理平台的搭建与实现 基于 Java 的图书馆综合管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/12 6:29:36

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/12 14:32:17

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/12 6:37:43

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

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

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

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