尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

iOS - RunLoop 相关知识点

iOS - RunLoop 相关知识点
📅 发布时间:2026/6/19 12:47:11

为什么要有 RunLoop?

  • 背景:线程执行完任务就会退出,但主线程(或者一些后台线程等)我们希望它能够一直存在、持续等待事件(触摸、定时器、网络回调等)。

原始的解决方案:

  • 如果写成 while(1) {} 类似的死循环,会出现问题:
    • 线程会持续占用 CPU(忙等待),浪费资源。

🍎给出的答案:

  • 👉 RunLoop —— 让线程在有事件时被唤醒执行,没有事件时进入休眠。
    • 在没有 RunLoop 的情况下,线程要么退出、要么忙等,无法高效等待事件。
    • Apple 的设计是 —— 让线程进入一个由内核管理的 事件循环系统,当有事件发生时唤醒线程,没有事件时进入休眠。
    • 这就是 RunLoop 的核心思想:事件驱动 + 内核唤醒 + 自动调度。

核心概念

本质:runloop本质是一个基于 事件驱动 的循环机制,底层依托内核的 mach port 进行等待和消息分发。用于 调度 定时器、输入源和观察者,让线程在“有事活跃、无事休眠”之间高效切换。

核心功能:

  1. 保持程序的持续运行(长连接、后台任务等)
  2. 监听 App 中的各种事件(触摸、滑动、定时器事件等)
  3. 自动管理 AutoreleasePool
  4. 控制线程的状态,节省 CPU 资源,提高程序性能

好处:相较于 线程轮询,runloop的工作机制效率更高。它允许在闲置时将线程置于睡眠状态,CPU进入低功耗状态,从而节约能源资源。

地位:在 iOS 中,RunLoop 是整个系统事件机制的核心支柱。主线程的 RunLoop 是 UIKit 事件响应、定时器、动画、触摸、AutoreleasePool、GCD 主队列 的共同基础。


组成角色

image

/// CFRunLoop 核心成员struct __CFRunLoop {CFMutableSetRef _commonModes;CFMutableSetRef _commonModeItems;CFRunLoopModeRef _currentMode;CFMutableSetRef _modes;mach_port_t _wakeUpPort;  // 唤醒 RunLoop 的系统端口
};

一、输入源 (Sources)

  • Source0(非端口事件): 用户态事件源,App内部事件,不能自动唤醒 runloop,需要手动 signal。
    • 特点:
      • 不依赖系统内核或端口。
      • RunLoop 不会被系统自动唤醒,必须手动调用 signal + wakeup
      • 常用于线程间通信 或 自定义事件调度
    • 例:
      • performSelector: onThread::
        1. 系统把 selector 封装事件为 source0。
        2. 加入到目标线程 runloop 的 source0 队列。
        3. 内部调用唤醒 signal + wakeup 唤醒 runloop。
          • 如果目标线程 runloop 正在执行,依然等待到下一轮循环的 source0 阶段再执行。
  • Source1(端口事件): 内核级事件源,由RunLoop和内核管理,Mach port驱动。
    • 特点:
      • 依赖系统的端口(mach port),用于接收内核或者其他线程发送的消息。
      • 用于系统级事件(触摸、网络)或者跨线程通信(CFMessagePort、CFMachPort)。
      • 当有端口消息到达时,内核通过 mach_msg 唤醒 RunLoop 所在线程。
    • 例:
      • UI事件:触摸、点击(基于 mach port 通知主线程 runloop)
      • 网络事件:Socket 数据到达 → 内核通知 → Source1 被唤醒 → 调用回调处理数据。
      • Port 消息:NSMachPort、CFMessagePort 等。

二、定时器 (Timers)

独立于 source 的一种事件类型。

  • 机制:
    • runloop 维护的一组定时器(NSTimer/performSelector:afterDelay:)。
    • 每轮循环时检查定时器的 “下一次触发时间”。
    • 如果到达了触发点,执行对应回调。
    • 若 runloop 处于休眠状态,内核会在最近一个 Timer 到期时唤醒线程。
  • 注意:
    • Timer 属于 runloop 的事件调度机制,不依赖 mach port 消息。

三、观察者 (Observers)

image

监听 RunLoop 状态变化的钩子(CFRunLoopObserver)

  • 用途:
    • 监听 runloop 各个阶段(Entry/BeforeTimers/BeforeSources/BeforeWaiting/AfterWaiting/Exit)。
    • 常用于:
      • 性能调试(卡顿监测)
      • 帧率刷新(CADisplayLink 内部机制)
      • 自动管理 AutoreleasePool(系统就在此阶段创建/销毁池子)

四、模式 (Modes)

image

每个 RunLoop 必须且只能运行在一个 Mode 下(Mode 可切换)。
每个 Mode 定义了 RunLoop 本轮循环中要监听哪些事件源(Sources、Timers、Observers)。

为什么需要 Mode ?

  • 因为不同场景下需要“隔离事件源”。
  • 比如用户滚动时,RunLoop 会切换到 tracking 模式以屏蔽 default 模式下的定时器。

常用 Mode

标题 描述 应用场景
NSDefaultRunLoopMode 默认模式 普通任务、定时器
UITrackingRunLoopMode UI追踪模式 滚动、拖拽时系统使用
NSRunLoopCommonModes 一种“标记集合”(非真正的Mode) 用来把 Source/Timer 同时加入多个 Mode
GSEventReceiveRunLoopMode 系统底层模式 接收系统级输入事件(触摸、键盘)
kCFRunLoopCommonModes Core Foundation 层关键字 与 NSRunLoopCommonModes 一致
  1. NSDefaultRunLoopMode

    • 主线程默认运行在该模式下。
    • 通过 NSTimer、performSelector:afterDelay: 创建的定时器都会加入这个 Mode。
    • 缺点:当用户滚动 UIScrollView 时,RunLoop 会自动切换到 UITrackingRunLoopMode,导致 Default 模式下的 Timer 暂停。
    • 例子:
      • Source0: performSelector:onThread:
      • Timer: NSTimer、afterDelay:
      • Observer: AutoreleasePool create/drain(注册在 CommonModes 下)
  2. UITrackingRunLoopMode

    • 用户触摸、滚动、拖动页面时,runloop 会临时切换到 tracking 模式。
    • 滚动结束后,runloop 会自动切回 Default 模式。
    • 例子:
      • Source1: 触摸 / 滑动事件(基于 Mach port)
      • Observer: AutoReleasePool create/drain(注册在 CommonModes 下)
  3. NSRunLoopCommonModes

    • 不是 Mode,而是一个“模式集合标签”

    • 任何被加入到 CommonModes 的 Source/Timer,会同时参与多个 Mode。

    • 例子:

      • 让某些 NSTimer 同时参与 default + tracking。
      NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {NSLog(@"Tick");
      }];
      [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
      
  4. 其他内部 Mode

    • UIInitializationRunLoopMode: App 启动阶段使用(私有)。
    • GSEventReceiveRunLoopMode:CoreGraphics 层,用于接收系统级输入事件(触摸、键盘)。
    • kCFRunLoopCommonModes:CFRunLoop 层关键字,等价于 NSRunLoopCommonModes。

模式之间的关系与区别

从上述文章看来,UITrackingRunLoopMode 和 GSEventReceiveRunLoopMode 都参与了 UI 事件处理,那他们是否冲突呢?

模式 所属层级 作用 谁在用?
GSEventReceiveRunLoopMode CoreGraphics/CoreAnimation 层 接收来自内核(Mach Port)的原始触摸、键盘事件 系统私有
UITrackingRunLoopMode UIKit 层 当用户开始滚动/拖拽时,UIKit 临时切换到此模式来追踪触摸事件 UIKit 控制的住 runloop
[ 内核 mach_msg ]↓
[ CoreGraphics 接收原始触摸事件 ] (GSEventReceiveRunLoopMode)↓
[ UIKit 处理触摸与滚动逻辑 ](UITrackingRunLoopMode)↓
[ App 代码响应事件 ]

从他们的分工与所属不难看出,他们并不冲突,而是事件分发链路的上下层关系。

GSEventReceive 在底层接收输入,UITracking 在上层进行滚动和触摸追踪。


RunLoop 的工作原理

image


线程与 RunLoop

特性 主线程 runloop 子线程 runloop
自动创建 ✅ ❌
自动启动 ✅ ❌需要手动启动
自动管理 Pool ✅ ❌
生命周期 app退出 手动退出、线程结束
常见用途 UI事件、主队列任务、动画刷新 后台任务、定时器、网络回调、自定义输入源
  1. 子线程为什么 Timer 不会触法?
    答:子线程没有 runloop 或没有进入循环。timer 依赖于 runloop,需要手动启动。
  1. 为什么 runloop 让线程“有事活跃,无事休眠”?
    答:sleep 阶段阻塞在内核消息等待上,不消耗 CPU。

AutoreleasePool 与 RunLoop

AutoreleasePool:内存回收的延迟释放机制

RunLoop:基于事件驱动的循环机制

总体关系:RunLoop 在每一轮循环中自动创建和销毁 AutoreleasePool,以此用来管理这一轮产生的 autorelease 对象,确保在循环结束时被及时释放。

线程与 AutoreleasePool 栈

  • 每个线程都有自己的 AutoreleasePool 栈结构(由 runtime 维护),哪怕没有显示写 @autoreleasepool。
  • 但是线程启动时栈是空的,只有在执行 autorelease 操作时,系统才会 懒加载创建池页(AutoreleasePoolPage) 来接收对象。

主线程的行为

  • 主线程拥有默认的 RunLoop(由系统在 UIApplicationMain 启动时创建)。
  • 主线程 RunLoop 由 UIKit 注册并托管,含有 pool observer,自动在每轮循环创建/销毁 AutoreleasePool。
RunLoop 阶段 行为
Entry 创建新的 AutoreleasePool
BeforeWaiting 销毁旧 Pool 并新建一个空 Pool(防止长时间未释放)
Exit 销毁当前 Pool
  • 因此,主线程在每一轮事件循环结束时,都会清理掉该轮产生的临时对象。

子线程的行为

  • 子线程默认 没有 RunLoop,系统也不会自动为它管理 AutoreleasePool。

    • 子线程 RunLoop 默认也没有 pool observer,不会自动管理 autoreleasepool,,因此必须在启动前包一层 @autoreleasepool,否则 RunLoop 循环期间 autorelease 对象不会及时释放, 直到线程退出才清空,可能引发内存峰值或泄漏。
    - (void)startBackgroundThread {NSThread *thread = [[NSThread alloc] initWithBlock:^{@autoreleasepool {[[NSRunLoop currentRunLoop] run];}}];[thread start];
    }
    
  • 但是子线程仍有 Pool 栈结构,只是如果不手动创建 Pool,也没有启动 RunLoop,autorelease 对象会延迟到线程结束才被释放。

    • 若希望子线程能够周期性释放对象,有 2 种做法:
      1. 手动添加 @autoreleasepool;
      2. 启动该线程的 RunLoop,让系统周期性创建和销毁 Pool。

GCD 与 RunLoop

GCD:线程的“任务调度机制”

RunLoop:线程的“事件循环机制”

总体关系:GCD 负责把任务派发到线程,RunLoop 负责让线程保活,并在空闲时处理事件,两者是协同关系。

GCD 与 RunLoop 的底层交互机制

GCD 和 RunLoop 的通信,是通过 Mach port 完成的。

  1. 当调用 dispatch_async(dispatch_get_main_queue(), block);
  2. 系统把 block 放入主队列;
  3. 发送 Mach 消息,通知主线程的 RunLoop;
  4. RunLoop 被唤醒;
  5. RunLoop 调用 _dispatch_main_queue_callback_4CF();
  6. 执行队列里的 GCD block。

所以 RunLoop 驱动 GCD 主队列的执行,没有 RunLoop,主线程就不会被唤醒,主队列的任务也不会被执行。

主线程上的关系

  • 主线程的 RunLoop 是由系统自动创建并运行的(在 UIApplicationMain 内部)。
  • 同时,主线程上也有 GCD 主队列(Main Queue)。

image

当你向主队列派任务时,任务不会立即执行,而是由系统通过 RunLoop 的唤醒机制唤醒主线程,然后在 RunLoop 的 AfterWaiting 阶段执行这些主队列任务。

简言之: 主线程的 GCD 主队列,是通过 RunLoop 驱动执行的。

/// 二者关系:
[ GCD Main Queue ]↓
[ dispatch_async(dispatch_get_main_queue(), ^{ ... }); ]↓
[ 主线程执行任务(依靠 RunLoop 唤醒)]

子线程上的关系

  • 子线程默认没有 RunLoop,因此 GCD 线程池 中的 线程 在执行完任务后就会销毁;
  • 如果想让子线程 “常驻”,就要显示启动 RunLoop,然后 GCD 派发的任务才能在子线程上 持续被处理。

参考链接

  • https://cloud.tencent.com/developer/article/1630860
  • https://zhuanlan.zhihu.com/p/467099321

相关新闻

  • 2025年热门的单轴芯导轨行业内口碑厂家排行榜
  • 2025年质量好的干选系统选煤设备厂家最新实力排行
  • 2025年知名的蜗轮蜗杆升降机最新TOP厂家排名

最新新闻

  • 连云港GEO服务商代理加盟选型靠谱推荐哪家强?2026年连云港GEO优化服务商代理加盟排名与合作权益深度解析 - 小随科技
  • 2026年6月母线槽厂家推荐,高压型母线槽/封闭型母线槽/铝合金外壳母线槽/防火浇筑型母线槽,母线槽安装门店哪家好 - 品牌推荐师
  • Linux QT开发:从零构建MQTT客户端应用
  • 寮步镇Shopee选品培训:东南亚热销品深度分析 - 东莞选校指南
  • 上海配眼镜适合什么人?四类典型人群的镜片适配方案 - 配眼镜新资讯
  • 跨省寄快递省钱攻略 2026各家物流价格对比与技巧 - 快递物流资讯

日新闻

  • 5分钟掌握Python进化算法:Geatpy高性能优化工具完全指南
  • Microchip 24AA044 EEPROM选型与应用全指南:从参数解析到实战编程
  • 华为的鸿蒙到底有多牛?为什么称作遥遥领先?

周新闻

  • 3步解锁iOS设备:applera1n激活锁绕过完全指南
  • 39 2026 人工智能证书终极盘点,普通人选 AI 证书可以从这些方向入手
  • Redis 暴露公网有多危险?从端口检查到补救步骤

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号