iOS--RunLoop原理

前言

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

项目中添加了一个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 处理,注意定时器的容忍度和长时间任务对动画和定时器的影响。

RunLoop实践

1.runloop可以做什么

  • 处理Crash(程序崩溃不退出)
  • 保持线程存活(线程保活)
  • 监测和优化App的卡顿

线程保活(NSOperation和GCD一样可以)(NSCondition加锁保活,不涉及RunLoop)
​ 如果项目需求比较复杂,很多操作都需要在子线程进行,比如有很多耗时操作(图片绘制,视频下载等等),子线程执行完任务之后会自动销毁,频繁的线程创建和销毁会导致资源浪费,此时就可以使用RunLoop进行线程保活而不被销毁。我们知道,当子线程中的任务执行完毕之后就被销毁了,那么如果我们需要开启一个子线程,在程序运行过程中永远都存在,那么我们就会面临一个问题,如何让子线程永远活着,这时就要用到常驻线程:给子线程开启一个RunLoop 注意:子线程执行完操作之后就会立即释放,即使我们使用强引用子线程使子线程不被释放,也不能给子线程再次添加操作,或者再次开启。 子线程开启RunLoop的代码,先点击屏幕开启子线程并开启子线程RunLoop,然后点击button。

2.runloop组成

sources/timer/observer(卡顿检测)

其中我们可以通过 RunLoop 的 Observer 机制来检测卡顿。具体思路是:

监听 RunLoop 的状态变化。当 RunLoop 处于不同状态时,比如准备处理 Timer、Source、等待事件(进入休眠)、处理事件后等,都可以通过注册相应的回调函数来监听。

监控 RunLoop 在处理任务时的时间消耗。如果某个状态持续时间过长(例如在 BeforeWaiting 或 AfterWaiting 状态之间执行任务时超过了一定的阈值),可以认为主线程出现了卡顿。

卡顿优化的话,可以通过将耗时任务移到后台线程、合理使用 NSTimer(适当调整 tolerance(宽容度),可以减少系统资源消耗)、推迟界面更新(线程保活)等方式,可以有效优化卡顿现象,提升应用的流畅度。

影响卡顿的因素:

卡顿跟硬件有关:CPU、GPU

影响CPU性能:IO任务,过多的线程抢占CPU资源、温度过高降频

影响GPU性能:显存频率、渲染算法、大计算量

参考:

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.mfbz.cn/a/883947.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于深度学习的花卉智能分类识别系统

温馨提示:文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 传统的花卉分类方法通常依赖于专家的知识和经验,这种方法不仅耗时耗力,而且容易受到主观因素的影响。本系统利用 TensorFlow、Keras 等深度学习框架构建卷积神经网络&#…

【Linux:共享内存】

共享内存的概念: 操作系统通过页表将共享内存的起始虚拟地址映射到当前进程的地址空间中共享内存是由需要通信的双方进程之一来创建但该资源并不属于创建它的进程,而属于操作系统 共享内存可以在系统中存在多份,供不同个数,不同进…

推荐5款压箱底的宝贝,某度搜索就有

​ 今天要给大家推荐5款压箱底的宝贝软件了,都是在某度搜索一下就能找到的好东西。 1.桌面壁纸——WinDynamicDesktop ​ WinDynamicDesktop是一款创新的桌面壁纸管理工具,能根据用户的地理位置和时间自动更换壁纸。软件内置多个美丽的动态壁纸主题&am…

苹果电脑系统重磅更新——macOS Sequoia 15 系统 新功能一 览

有了 macoS Sequoia,你的工作效率将再次提升:快速调整桌面布局,一目了然地浏览网页重点,还可以通过无线镜像功能操控你的iPhone。 下面就来看看几项出色新功能,还有能够全面发挥这些功能的 App 和游戏。 macOS Sequo…

智能新突破:AIOT 边缘计算网关让老旧水电表图像识别

数字化高速发展的时代,AIOT(人工智能物联网)技术正以惊人的速度改变着我们的生活和工作方式。而其中,AIOT 边缘计算网关凭借其强大的功能,成为了推动物联网发展的关键力量。 这款边缘计算网关拥有令人瞩目的 1T POS 算…

使用build_chain.sh离线搭建匹配的区块链,并通过命令配置各群组节点的MySQL数据库

【任务】 登陆Linux服务器,以MySQL分布式存储方式安装并部署如图所示的三群组、四机构、 七节点的星形组网拓扑区块链系统。其中,三群组名称分别为group1、group2和group3, 四个机构名称为agencyA、agencyB、agencyC、agencyD。p2p_port、cha…

powerbi计算销售额累计同比增长率——dax

目录 效果展示: 一、建立日期表 二、建立度量值 1.销售收入 2.本年累计销售额 3.去年累计销售额 4.累计同比增长率 三、矩阵表制作 效果展示: 数据包含2017-2019年的销售收入数据 一、建立日期表 日期表建立原因及步骤见上一篇文章https://blog…

数据处理与统计分析篇-day11-RFM模型案例

会员价值度模型介绍 会员价值度用来评估用户的价值情况,是区分会员价值的重要模型和参考依据,也是衡量不同营销效果的关键指标之一。 价值度模型一般基于交易行为产生,衡量的是有实体转化价值的行为。常用的价值度模型是RFM RFM模型是根据…

UNI-SOP应用场景(1)- 纯前端预开发

在平时新项目开发中,前端小伙伴是否有这样的经历,hi,后端小伙伴们,系统啥时候能登录,啥时候能联调了,这是时候往往得到的回答就是,再等等,我们正在搭建系统呢,似曾相识的…

20个数字经济创新发展试验区建设案例【2024年发布】

数据简介:国家数字经济创新发展试验区的建设是一项重要的国家战略,旨在推动数字经济与实体经济的深度融合,促进经济高质量发展。自2019年10月启动以来,包括河北省(雄安新区)、浙江省、福建省、广东省、重庆…

通过OpenScada在ARMxy边缘计算网关上实现数字化转型

随着工业4.0概念的普及,数字化转型已成为制造业升级的关键路径之一。在此背景下,边缘计算技术因其能够有效处理大量数据、减少延迟并提高系统响应速度而受到广泛关注。ARMxy边缘计算网关,特别是BL340系列,凭借其强大的性能和灵活的…

Linux网络之UDP与TCP协议详解

文章目录 UDP协议UDP协议数据报报头 TCP协议确认应答缓冲区 超时重传三次握手其他问题 四次挥手滑动窗口流量控制拥塞控制 UDP协议 前面我们只是说了UDP协议的用法,但是并没有涉及到UDP协议的原理 毕竟知道冰箱的用法和知道冰箱的原理是两个层级的事情 我们首先知道计算机网…

使用API有效率地管理Dynadot域名,设置域名服务器(NS)

前言 Dynadot是通过ICANN认证的域名注册商,自2002年成立以来,服务于全球108个国家和地区的客户,为数以万计的客户提供简洁,优惠,安全的域名注册以及管理服务。 Dynadot平台操作教程索引(包括域名邮箱&…

在虚幻引擎中实现Camera Shake 相机抖动/震屏效果

在虚幻引擎游戏中创建相机抖动有时能让画面更加高级 , 比如 遇到大型的Boss , 出现一些炫酷的特效 加一些短而快的 Camera Shake 能达到很好的效果 , 为玩家提供沉浸感 创建Camera Shake 调整Shake参数 到第三人称或第一人称蓝图 调用Camera Shake Radius值越大 晃动越强

拍卖的价格怎么定?聊聊转转拍卖场的起拍定价算法演变

价格策略、定价调价算法是诸多中大规模电商不可或缺的一项能力,涉及到精准定价、智能调价、智能发券、成本控制等一系列智能运营场景,尤其对于二手行业来说,定价能力更是面临诸多挑战,却又不可或缺。本文将旨在介绍转转 TOB 拍卖场…

kibana开启访问登录认证

编辑es配置文件,添加以下内容开启es认证 vim /etc/elasticsearch/elasticsearch.yml http.cors.enabled: true http.cors.allow-origin: "*" http.cors.allow-headers: Authorization xpack.security.enabled: true xpack.security.transport.ssl.enable…

解释器模式原理剖析和Spring中的应用

解释器模式原理剖析和Spring中的应用 解释器模式 是一种行为型设计模式,它定义了一种语言的文法表示,并提供了一个解释器来处理该文法的表达式。解释器模式可以用于构建语法解释器,例如计算器、简单编程语言的解释器等。 核心思想&#xff1a…

Java框架学习(mybatis)(01)

简介:以本片记录在尚硅谷学习ssm-mybatis时遇到的小知识 详情移步:想参考的朋友建议全部打开相互配合学习! 官方文档: MyBatis中文网https://mybatis.net.cn/index.html 学习视频: 067-mybatis-介绍和对比_哔哩哔…

人工智能时代,程序员如何保持核心竞争力?

引言 随着AIGC(如ChatGPT、Midjourney、Claude等)大语言模型接二连三的涌现,AI辅助编程工具日益普及,程序员的工作方式正在发生深刻变革。有人担心AI可能取代部分编程工作,也有人认为AI是提高效率的得力助手。面对这一…

一天面了8个Java后端,他们竟然还在背5年前的八股文!

今天面了8个Java候选人,在面试中我发现他们还停留在面试背八股文的阶段,5年前面试背八股文没问题,随着市场竞争越来越激烈,再问普通的Java八股文已经没有意义了,因为考察不出来获选人的真实实力! 现在面试…