iOS--RunLoop原理

news2024/9/27 17:22:32

前言

曾经在写项目的时候遇到过这么一个问题。:

项目中添加了一个tableview,然后还有一个计时器,当滑动tableview的时候会阻塞计时器,你得执行这么一段代码后,计时器才能正常运行。

RunLoop.current.add(timer, forMode: .common)

发生这种情况是因为我在defaultRunLoopMode上隐式创建计时器,这实际上是我们应用程序的主线程。然后,当用户积极与我们的用户界面互动时,这将暂停,然后在他们停止时重新激活。

什么是runloop呢?

RunLoop是一个事件循环机制,用于管理线程中的事件和消息。它允许线程在没有任务的情况下休眠,并在有任务需要处理时唤醒线程。 RunLoop主要负责以下几个方面:

  • 处理输入源:RunLoop负责处理输入源,包括用户界面事件、触摸事件、定时器事件、网络事件等,通过RunLoop能够有效地处理这些事件,让应用程序的响应更加及时。

  • 保持线程活动(线程保活):RunLoop能够保持线程活动,即使在没有任务时,RunLoop也会让线程休眠而不会退出,以便随时处理来自输入源的事件。

  • 定时器功能:RunLoop提供了一些定时器功能,例如延迟执行和重复执行某个任务。通过定时器功能,可以很方便地在指定时间执行任务。

  • 优化性能:RunLoop能够优化应用程序的性能,通过RunLoop能够让应用程序在有任务需要处理时及时唤醒线程,而在没有任务时让线程休眠,从而避免了线程的空转,减少了CPU的占用,提高了应用程序的性能。

runloop和线程之间的关系

  • 每条线程都有唯一的一个与之对应的RunLoop对象
  • RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
  • 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
  • RunLoop会在线程结束时销毁
  • 主线程的RunLoop已经自动获取(创建,用于响应UI和用户交互事件),子线程默认没有开启RunLoop

runloop的基本组成

  • Input Source(输入源):

        异步事件的通道,如用户交互(手势、触摸)、网络请求等。

  • Timer Source(定时器源):

        定时任务,如 NSTimer,它会定期触发某个操作。RunLoop 会定期检查定时器,并在时间到达时触发相应的操作。

  • Observer(观察者):

        监听 RunLoop 状态的变化,如启动、休眠、唤醒、退出等。可以用于监控 RunLoop 的执行过程,便于调试或优化。

        以下是Observer(观察者)的常见状态:

  1. kCFRunloopEntry (runloop准备启动)
  2. kCFRunloopBeforeTimers (通知观察者,runloop将要对Timer的一些相关事件进行处理了)
  3. kCFRunloopBeforeSources (将要处理一些Sources事件)
  4. kCFRunloopBeforeWaiting( 即将要发生用户态到内核态的切换 用户态 —> 内核态)没事做进入内核态避免资源浪费
  5. kCFRunloopAfterWaiting (内核态—转—>用户态)
  6. kCFRunloopExit (runloop退出通知)

        简单来说就是,runloop从用户态切换到内核态可以节省系统资源,使得线程在没有任务时不会浪费 CPU 时间。

RunLoop 通过监听输入源、定时器源、观察者来处理和调度事件,确保线程可以在处理完事件后及时进入休眠。

runloop的不同模式

runloop可以根据所需切换成不同的模式:

  1. NSDefaultRunLoopMode :默认模式,处理大多数应用事件,如 Timer定时器触发、网络事件等。
  2. UITrackingRunLoopMode :UI模式,专门处理UI事件
  3. NSRunLoopCommonModes :常用模式,允许 RunLoop 同时处理多种事件类型。使用场景包括当用户滚动 UIScrollView 时仍能处理定时器事件。
  4. UIInitializationRunLoopMode :在刚启动App时第进入的第一个Mode,启动完成后就不再使用
  5. GSEventReceiveRunLoopMode :接受系统事件的内部Mode

示例:

let timer = Timer(timeInterval: 1.0, repeats: true) { _ in
    print("Timer fired during scroll")
}
//将定时器添加到 .common 模式中,确保在滚动时仍能触发
RunLoop.current.add(timer, forMode: .common)

将Timer定时器添加到.common模式中才能在ScrollView滚动时响应的原理:

  • 当应用处于空闲状态时,RunLoop 处于 NSDefaultRunLoopMode(默认模式),它处理正常任务,包括 Timer 的回调、用户输入等。
  • 当用户与 ScrollView 交互时,RunLoop 切换到 UITrackingRunLoopMode(UI模式),在这种模式下,RunLoop 只处理与用户交互相关的任务,确保滑动的流畅性。此时,未被标记为 “Common” 的 Timer 和其他事件源会被暂时忽略。
  • 如果 Timer 只添加到 NSDefaultRunLoopMode(默认模式),滑动时(UITrackingRunLoopMode)它将不会触发,直到滑动结束后 RunLoop 切换回 DefaultMode,Timer才会被继续触发。
  • 使用 NSRunLoopCommonModes(常用模式) 可以让 Timer 在所有标记为 “Common” 的模式下执行,保证用户滑动时依然能触发回调。

tips:Common模式默认将runloop添加到NSDefaultRunLoopMode和UITrackingRunLoopMode模式。

runloop的循环流程

  1. 检查并处理输入源事件。
  2. 检查并处理定时器事件。
  3. 如果没有任务,进入休眠等待新的事件。
  4. 当有事件到来时,唤醒并处理事件。
  5. 重复以上过程,直到 RunLoop 退出。

用户态: 是指应用程序执行的状态。RunLoop 在这个状态下负责处理任务(如用户交互、定时器)。

内核态: 当 RunLoop 没有任务时,它会进入内核态,等待新的事件。这个时候,线程会进入休眠,操作系统负责监控任务的到达,以减少 CPU 资源的消耗。

runloop的Input Source(输入源)分类

1.Source0(非基于 port 的事件源):

        Source0 是应用程序主动触发的事件源。它不依赖系统的 port 机制,也不会自动唤醒 RunLoop。这类事件通常是应用内部主动触发的事件,比如用户点击了按钮、触摸屏幕或某个任务完成后调用的回调函数。

特点:

  • 需要手动触发。
  • RunLoop 无法通过 Source0 自动唤醒,必须配合其他机制(如 Source1)来激活。

2.Source1(基于 port 的事件源):

        Source1 是基于 port 机制的事件源,用于处理操作系统内核级别的事件它负责监听操作系统内核发出的事件或消息(如网络 I/O、文件 I/O 等),并通过 port 机制唤醒 RunLoop,然后分发这些事件进行处理。

特点:

  • 基于 port 通信,可以自动唤醒 RunLoop。
  • 用于系统内部和外部进程的消息传递。

总的来说,Source0负责处理应用程序内部的主动事件(用户点击、任务回调等),需要其他机制(Soruce1)唤醒 RunLoop。而Source1 处理基于 port 的系统内核事件(例如进程间通信、网络事件等),并负责唤醒 RunLoop。

举个例子:

我们触摸屏幕,屏幕表面的事件会先包装成Event,Event先告诉source(输入源)(通过mach_port机制),Source1唤醒RunLoop,然后将事件Event分发给Source0,然后由Source0来处理。

事件处理完成后,如果没有新的事件或定时器触发,RunLoop 会再次进入休眠状态,直到下一个事件发生。

iOS使用RunLoop实现的功能

AutoreleasePool

自动释放池的创建和释放,销毁的时机如下所示:

  • kCFRunLoopEntry; // 进入runloop之前,创建一个自动释放池
  • kCFRunLoopBeforeWaiting; // 休眠之前,销毁自动释放池,创建一个新的自动释放池
  • kCFRunLoopExit; // 退出runloop之前,销毁自动释放池

 RunLoop 机制的事件响应流程

1. 硬件事件的产生和传递

当一个硬件事件发生时(例如用户触摸屏幕、按下按钮、摇晃设备等),硬件事件被系统底层的 IOKit.framework 捕获,并生成一个 IOHIDEvent 事件。这个事件首先被 SpringBoard 进程接收。SpringBoard 是 iOS 的系统进程,负责处理与设备交互相关的硬件事件,例如锁屏、静音等。

然后,SpringBoard 通过 mach_port 将事件传递给目标 App 的进程。App 中的 RunLoop 会监听来自系统的这些事件。当事件到达时,RunLoop 中注册的 Source1 事件源被触发,唤醒 RunLoop,并执行回调来处理这些事件。

2. _UIApplicationHandleEventQueue() 函数

在事件进入应用进程后,_UIApplicationHandleEventQueue() 函数负责对事件进行处理和分发。例如:

• 将触摸事件包装成 UIEvent 对象。

• 识别触摸的手势是否属于点击、拖拽、缩放等。

• 处理 UI 的交互,比如按钮点击、屏幕旋转等。

如果是常规事件,比如用户点击了一个按钮,系统会通过此函数找到事件的响应目标,比如 UIButton,并触发相应的回调函数。

手势识别

手势事件的识别过程:

• 当 _UIApplicationHandleEventQueue() 识别到某个手势时,它会取消当前的触摸事件处理(比如 touchesBegan、touchesMoved),并标记对应的 UIGestureRecognizer 为“待处理”状态。

• 苹果系统内部注册了一个 RunLoop 观察者,用来监听 BeforeWaiting 阶段(即 RunLoop 即将进入休眠状态)。当 RunLoop 进入这个状态时,观察者的回调 _UIGestureRecognizerUpdateObserver() 会被触发。

• 在这个回调中,系统会处理所有刚标记为“待处理”的手势识别器,执行它们的相应回调函数。比如,用户的双击、滑动等手势就是在这个阶段被识别并处理的。

总结: 手势识别是在 RunLoop 准备进入休眠前的最后阶段进行处理的,这样可以保证手势识别的优先级较高。

页面更新

当我们更新界面(例如修改视图的 frame,或调用 setNeedsLayout / setNeedsDisplay 方法)时,UIView 或 CALayer 会被标记为“待处理”,并加入到系统的待处理队列中。

苹果系统内部同样注册了一个 RunLoop 观察者,用来监听 BeforeWaiting 和 Exit 阶段。当 RunLoop 进入这些状态时,回调 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv() 被触发,系统会遍历所有标记为待处理的视图或图层,执行布局更新和重绘操作。

优化提示:

通过将界面更新操作推迟到 RunLoop 休眠前的阶段,可以避免在用户交互时立即刷新界面,从而提高界面响应速度。

定时器NSTimer

NSTimer 工作原理:

• NSTimer 是基于 RunLoop 的定时器,底层实现为 CFRunLoopTimerRef。当你创建一个 NSTimer 并将其添加到 RunLoop 中时,RunLoop 会为定时器注册好未来的触发时间点。

• 定时器回调并不总是会在非常准确的时间点触发,因为 RunLoop 会对定时器进行一些优化。定时器有一个 tolerance 参数,允许指定触发时间的最大误差。这样可以在保证性能的前提下,避免系统资源的浪费。

时间点的错过:

如果在某个时间点执行了一个较长的任务,RunLoop 可能会错过该时间点,直接跳到下一个时间点,而不会延后执行。例如,如果 NSTimer 设置为每 10 秒触发一次,但是某次因为执行长时间任务导致错过了 10:00 的触发点,那么定时器会直接在下一个 10:10 的时间点触发。

总的来说:

1. 事件响应:通过 Source1 唤醒 RunLoop,然后通过 Source0 分发事件。

2. 手势识别:在 BeforeWaiting 阶段处理,优先级较高。

3. 界面更新:在 BeforeWaiting 或 Exit 阶段进行 UI 布局和重绘优化。

4. 定时器:NSTimer 和 CADisplayLink 都通过 RunLoop 处理,注意定时器的容忍度和长时间任务对动画和定时器的影响。

参考:

https://github.com/miaoqiu/RunLoop?tab=readme-ov-file#%E6%8C%89%E7%85%A7%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3source%E7%9A%84%E5%88%86%E7%B1%BB

https://github.com/yanmingLiu/iOSNotes?tab=readme-ov-file#3-runloop

Runloop解析_objective-c runloop-CSDN博客

RunLoop in details | kyryl horbushko

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2170562.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis 的 Java 客户端有哪些?官方推荐哪个?

Redis 官网展示的 Java 客户端如下图所示,其中官方推荐的是标星的3个:Jedis、Redisson 和 lettuce。 Redis 的 Java 客户端中,Jedis、Lettuce 和 Redisson 是最常用的三种。以下是它们的详细比较: Jedis: 线程安全&…

springboot在线教学平台

基于springbootvue实现的在线教学平台 (源码L文ppt)4-069 4.1系统结构设计 这些功能可以充分满足在线教学平台的需求。此系统功能较为全面如下图系统功能结构如图4-1所示。 图4-1功能结构图 4.2系统功能模块设计 在线教学平台的使用者主要有二类…

AI视频技术:引领影视剧拍摄的未来

大家好,我是Shelly,一个专注于输出AI工具和科技前沿内容的AI应用教练,体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具,拥抱AI时代的到来。 当科技遇见艺术,一场视听盛宴正…

华为GaussDB数据库之Yukon安装与使用

一、Yukon简介 Yukon(禹贡),基于openGauss、PostgreSQL、GaussDB数据库扩展地理空间数据的存储和管理能力,提供专业的GIS(Geographic Information System)功能,赋能传统关系型数据库。 Yukon 支…

破局汽车智能化浪潮:Tire 1供应商的网络优化与升级策略

在汽车行业经历电动化、智能化的深刻变革中,Tier 1供应商正面临着前所未有的挑战与机遇。Tier 1 供应商,即一级供应商,是汽车产业链中占据关键地位的合作伙伴。这类供应商不仅直接向整车制造商提供核心总成和模块,还深度参与整车的…

ISSCC 34.8 用于AI边缘设备的22nm,31.2TFLOPS/W,16Mb ReRAM存内浮点计算架构

本文将分享存内浮点计算前沿论文——ISSCC 2024《34.8 A 22nm 16Mb Floating-Point ReRAM Compute-in-Memory Macro with 31.2TFLOPS/W for AI Edge Devices》。下面将从文章基本信息、创新点解析、芯片测试与对比及未来展望四个部分展开介绍。 基本信息介绍 1、研究背景及面临…

QualiMap:一款强大的二代测序比对文件质控工具

在生物信息学中,数据质量的评估和可视化是很重要的一环。今天我们来聊聊一个常用的工具——Qualimap,它是一个用于评估高通量测序数据质量的开源软件,尤其是对RNA-seq和DNA测序数据的分析非常友好。无论你是本科生还是刚接触生物信息学的新人…

阿博图书馆管理:SpringBoot实战指南

第二章 开发技术介绍此次B/S结构、Java技术以及mysql数据库是该阿博图书馆管理系统的主要开发技术,然后对系统的整体设计、数据库设计、功能模块设计、系统页面设计以及系统程序设计进行了详细的研究与规划。 2.1 系统开发平台 在该阿博图书馆管理系统中&#xff0c…

大学学校用电安全远程监测预警系统

1.概述: 该系统是基于移动互联网、云计算技术,通过物联网传感终端,将办公建筑、学校、医院、工厂、体育场馆、宾馆、福利院等人员密集场所的电气安全数据,实时传输至安全用申管理服务器,为用户提供不间断的数据跟踪&a…

【Axure高保真原型】标签切换动态面板页面

今天和大家分享通过标签切换动态面板页面的原型模板,点击标签可以选择并且打开下方对应的人物详细页面。标签组是用中继器制作的,所以使用也很简单,只需要在中继器表格里填写标签名,就可以生成对应的标签;标签对应的内…

网通产品硬件设计工程师:汽车蓝牙收发器用网络隔离变压器有哪些选择呢?

Hqst盈盛(华强盛)电子导读:今天分享的是网通设备有关工程师产品设计时可供选择的两款汽车蓝牙收发器用网络隔离变压器... 下面我们就一起来看看网通设备有关工程师产品设计时可供选择的两款汽车蓝牙收发器用网络隔离变压器,让您的…

实习前学一学git

工作区 暂存区 本地仓库 远程仓库 git commit -m "提交信息" 提交的是暂存区里的内容,没有git add 的不会被提交到本地仓库

浅谈电气火灾监控系统在变电所的应用

摘要:阐述电气火灾监控系统在变电所的应用,电气火灾监控系统的管理措施,包括运行标准、运行模式、运行原则、警报阈值、监控显示。安科瑞叶西平1870*6160015 关键词:监控系统;警报阀值;运行模式;医院&…

findCirclesGrid检测不到圆点棋盘格技术原因分析与解决方案

为什么你检测不到圆点标定板 简介 某日,同事反映某厂的标定板无法识别,经过多次尝试,依旧失败。最后被总结为非标尺寸标定板导致。隐隐觉的这不是真正的原因,标定板本身可以自行设计成为各种不同的参数。这让我想起几年前也遇到…

在Windows上安装Git

一、下载Git安装包 访问Git官网:首先,你需要访问Git的官方网站下载安装包:在官网页面上,找到并点击“Downloads”按钮,然后选择“Windows”系统对应的安装包进行下载。安装包通常以.exe格式提供 二、安装Git 双击运…

错误解决 ---- Unexpected lexical declaration in case block no-case-declarations

1. 报错提示 154:15 error Unexpected lexical declaration in case block no-case-declarations154:21 error resId is assigned a value but never used no-unused-vars158:15 error Unexpected lexical declaration in case block no-case-declarations158:21 e…

Excel中如何批量删除括号里的内容

一、问题的缘起 微信群里有人问,如何在Excel中删除1列单元格中的括号和其中的人名,如下图所示。传统一个个删除,不仅太麻烦,还有可能出错。有没有什么便捷的办法呢,今天我们一起来分享五种不错的解决方法。 图示 二、…

突发!OpenAI CTO Murati 官宣离职

2024 年 9 月 26 日,OpenAI 首席技术官 Mira Murati 宣布将离开 OpenAI ,这一令人意外的举动标志着这家公司近期迎来另一名重要高管的离职。 Murati 在 X 上的一篇帖子中写道:“我离开公司是由于我想腾出时间和空间来进行自己的探索”&#x…

算法: 滑动窗口题目练习

文章目录 滑动窗口长度最小的子数组无重复字符的最长子串最大连续1个个数 III将x减到0的最小操作数水果成篮找到字符串中所有字母异位词串联所有单词的子串最小覆盖子串 总结 滑动窗口 长度最小的子数组 做这道题时,脑子里大概有个印象,知道要用滑动窗口,但是对于滑动窗口为什…

基于 LangChain 的自动化测试用例的生成与执行

在前面的章节中,分别介绍了 Web、App、接口自动化测试用例的生成。但是在前文中实现的效果均为在控制台打印自动化测试的用例。用例需要手动粘贴,调整之后再执行。 那么其实这个手动粘贴、执行的过程,也是可以直接通过人工智能完成的。 应用…