Runloop解析

RunLoop

前言

​ 本文介绍RunLoop的概念,并使用swift和Objective-C来描述RunLoop机制。

简介

​ RunLoop——运行循环(死循环),它提供了一个事件循环机制在程序运行过程中处理各种事件,例如用户交互、网络请求、定时器等等。 RunLoop可以在需要的时候自己跑起来运行,在没有操作的时候就停下来休息,充分节省CPU资源,提高程序性能。

基本思想

循环的处理事件。RunLoop在主线程运行,负责管理该线程中的事件,并确保UI更新等重要任务能够顺利执行,RunLoop启动时,后进入无限循环,等待事件发生。当有事件发生时,RunLoop会调用相应的处理方法来处理该事件,并继续等待下一个事件发生。RunLoop会一直运行,直到被手动停止或应用程序退出。

目的

——保证RunLoop所在线程不退出

——负责监听事件。iOS触摸、时钟、网络。

RunLoop与线程

​ 在iOS中,每个线程都有一个RunLoop(一一对应),但默认情况下,只有主线程RunLoop是开启的,其他线程都是禁用的。要使用RunLoop,必须手动启动它,并将其添加到线程的运行循环中。

RunLoop对象

Foundation框架 (基于CFRunLoopRef的封装) NSRunLoop对象

CoreFoundation CFRunLoopRef对象

NSRunLoop是基于CFRunLoopRef的一层OC封装

RunLoop运行模式

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

截屏2022-04-02 下午2.23.50.png

RunLoop优先处理UI模式的事件,而UI模式只能被UI事件唤醒

  1. NSDefaultRunLoopMode 默认模式 —— 一般处理timer\网络事件
  2. UITrackingRunLoopMode UI模式 —— 专门处理UI事件
  3. NSRunLoopCommonModes 占位模式( UI && 默认)
  4. UIInitializationRunLoopMode 在刚启动App时第进入的第一个Mode,启动完成后就不再使用
  5. GSEventReceiveRunLoopMode 接受系统事件的内部Mode

Timer:定时器

iOS开发中,定时器是一种常见的事件,例如每隔一段时间刷新UI、执行后台任务等等。RunLoop提供了定时器(timer)机制,用于在指定时间间隔内执行某个操作。

var timer1: Timer?override func viewDidLoad() {super.viewDidLoad()self.view.backgroundColor = .whitetestMode()}func testMode(){let scrollView = UIScrollView(frame: CGRect(x: 50.0, y: 100.0, width: 100.0, height: 100.0))scrollView.backgroundColor = .orangescrollView.contentSize = CGSize(width: 100.0, height: 200.0)self.view.addSubview(scrollView)timer1 = Timer(timeInterval: 2.0, target: self, selector: #selector(runLoopAction), userInfo: nil, repeats: true)RunLoop.current.add(timer1!, forMode: .common)}@objc func runLoopAction(){NSLog("=== run ===")}

运行结果:

使用 kCFRunLoopDefaultMode 模式时,滑动 UIScrollView 不打印.
使用 UITrackingRunLoopMode 模式时,只有滑动 UIScrollView 才会打印. 
使用 kCFRunLoopCommonModes 模式时,不管滑不滑动 UIScrollView 都会打印

**Source:**事件源

按照函数调用栈

  • Source0:非Source1 用于用户主动触发的事件(点击button 或点击屏幕)(数据结构:[machport:value])machport理解成进程间相互发送消息的一种机制。
  • Source1:基于port的系统内核事件,主动唤醒runloop(数据结构:数组)

简单举个例子:一个APP在前台静止着,此时,用户用手指点击了一下APP界面,那么过程就是下面这样的:

我们触摸屏幕,先摸到硬件(屏幕),屏幕表面的事件会先包装成Event,Event先告诉source(mach_port),source1唤醒RunLoop,然后将事件Event分发给source0,然后由source0来处理。

如果没有事件,也没有timer,则runloop就会睡眠,如果有,则runloop就会被唤醒,然后跑一圈。

Observer – CFRunLoopObserver:观察者

观察者可观察的时间点

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

这些可观察的时间点有时也可作为检测app卡顿的功能(例如渲染图片,从waiting之前一次一次渲染)。

Perform Selector

Perform Selector是一种调用方法的方式,可以在RunLoop中异步执行某个方法。在调用方法时,可以设置延迟执行时间和RunLoop模式。该方法会在指定的时间间隔内执行,直到被取消。

例如,要在主线程中使用Perform Selector,可以使用如下代码:

RunLoop.current.perform(#selector(doSomething), target: self, argument: nil, order: 0, modes: [.default])

这将在默认模式下异步执行doSomething方法。

事件循环的时间机制

截屏2022-03-14 下午2.16.59.png

  1. main函数—> UIApplicationMain
  2. 在UIApplicationMain中启动主线程的Runloop
  3. 即将进入Runloop(通知observer)
  4. 将要处理timer、source0事件(通知observer)
  5. 处理source0事件
  6. 如果有source1事件要处理(跳转到10)
  7. 线程将要休眠(通知observer)
  8. 休眠、等待唤醒(唤醒的方法:1 source1事件,2 Timer事件,3 外部手动唤醒)
  9. 线程刚被唤醒(通知observer)
  10. 处理唤醒时收到的消息
  11. 即将退出Runloop(通知observer)

Mode是如何切换的

首先我们来说是,mode是如何切换的 例如:scrollView 由静止到滑动,是如何由NSDefaultRunLoopMode变为UITrackingRunLoopMode

首先 我们要了解一下 CFRunLoopRunSpecific

CFRunLoopRunSpecific 是启动 Runloop 和指定 Runloop 在那个mode下执行的mode。这个函数一般是操作系统进行mode的切换。

比如滑动的时候,Runloop 会进入进入 UITrackingRunLoopMode,而app启动的时候UIInitializationRunLoopMode

每一个mode处理完成后,如果runloop没有退出,就会返回之前的mode,初始mode是default。

CFRunLoopRunSpecific 会保持前一次mode的状态属性(stopped和currentmode)然后发出即将要进入新的mode通知,然后进入__CFRunLoopRun(__CFRunLoopRun会创建一个循环),然后这个mode运行结束后再发已退出mode通知。再恢复前一次的 stopped 和 currentmode

RunLoop的常用操作

除了上述基本操作之外,RunLoop还提供了其他常用操作,例如:

  1. stop:停止RunLoop的运行。
  2. runUntilDate:运行RunLoop直到指定日期。
  3. runMode:运行RunLoop指定模式下的事件处理循环。
  4. currentMode:获取当前RunLoop的运行模式。

自动释放池

AutoreleasePool

App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()

第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

事件响应

苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。

当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发,触发Source0。

_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。

手势识别

当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。

苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。

当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。

界面更新

当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。

苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调去执行一个很长的函数: _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。

按钮点击

首先是由那个Source1 接收IOHIDEvent,之后在回调 __IOHIDEventSystemClientQueueCallback() 内触发的 Source0,Source0 再触发的 _UIApplicationHandleEventQueue()。所以UIButton事件看到是在 Source0 内的。

RunLoop与线程安全

iOS开发中,多线程是一个常见的问题。RunLoop在处理异步事件时,可能会导致线程不安全的问题。为了保证RunLoop的线程安全,可以使用以下方法:

  1. 使用RunLoopQueue,在队列中使用RunLoop来执行异步操作。
  2. 在主线程中使用RunLoop来处理异步事件,避免跨线程操作

q1: RunLoop可以做什么?

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

线程保活(NSOperation和GCD一样可以)(NSCondition加锁保活,不涉及RunLoop)

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

q2:线程和RunLoop什么关系?

**RunLoop存储方式:**键值对(线程 :runloop)

所以runloop和线程是一一对应的。

q3:RunLoop组成

Mode->sources/timer/observer(卡顿检测)

如果没有sources或timer直接进入休眠状态

**CFRunLoop和NSRunLoop区别:**CFRunLoop是在CoreFoundation中用纯c语言实现的,它提供一个c函数API,是线程安全的;而NSRunLoop是基于CF的封装,提供的是面向对象的API,非线程安全。

q4:RunLoop怎么启动

  1. run
  2. run(until)
  3. run(mode,until)

使用第三种,自己构造runloop循环,并且线程不能设置为强引用(或者自己设置为nil)

卡顿监测优化

卡顿跟硬件有关CPU、GPU

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

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

UIKit不是一个线程安全的框架,所以UI操作等都需要在主线程操作,故复杂任务一般放子线程执行,这也是线程保活意义所在。

**如何监测卡顿:**fps,59.94/s,ping,runloop

通过 CFRunLoopObserverRef来监测

处理时机:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

U

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

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

UIKit不是一个线程安全的框架,所以UI操作等都需要在主线程操作,故复杂任务一般放子线程执行,这也是线程保活意义所在。

**如何监测卡顿:**fps,59.94/s,ping,runloop

通过 CFRunLoopObserverRef来监测

处理时机:

[外链图片转存中…(img-kZRaHKGR-1700973808112)]

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HT97226 免输出电容立体声耳机放大器的应用与曲线

HT97226应用: ・耳机 ・多媒体音频接口 ・机顶盒 ・ 蓝光/DVD播放器 ・LCD电视 ・音频消费电子产品 HT97226应用图于曲线: HT97226是一款差分输入/单端输入、可直接输出驱动的耳机放大器。5V供…

网络渗透测试(认识)

ARP协议 逻辑地址变成物理地址 32bit的IP地址变换成48bit的mac地址 ARP两个字节(0x0806) ARP解析协议 每一个主机都有ARP高速缓存,此缓存中记录了最近一段时间的内其他IP地址与其MAC地址的对应关系 如果本机想与某台主机通信,首先…

Azure Machine Learning - 创建Azure AI搜索服务

目录 准备工作查找 Azure AI 搜索产品/服务选择订阅设置资源组为服务命名选择区域选择层创建服务配置身份验证扩展服务何时添加第二个服务将多个服务添加到订阅 Azure AI 搜索是用于将全文搜索体验添加到自定义应用的 Azure 资源,本文介绍如何创建Azure AI搜索服务 …

5.前端--CSS-基本概念【2023.11.26】

1. CSS 语法规范 CSS 规则由两个主要的部分构成:选择器以及一条或多条声明。 属性和属性值之间用英文“:”分开 多个“键值对”之间用英文“;”进行区分 选择器 : 简单来说,就是选择标签用的。 声明 :就是改变样式 2.CSS引入方式 按照 CSS 样…

网络运维与网络安全 学习笔记2023.11.26

网络运维与网络安全 学习笔记 第二十七天 今日目标 NAT场景与原理、静态NAT、动态NAT PAT原理与配置、动态PAT之EasyIP、静态PAT之NAT Server NAT场景与原理 项目背景 为节省IP地址和费用,企业内网使用的都是“私有IP地址” Internet网络的组成设备&#xff0c…

【办公软件】电脑开机密码忘记了如何重置?

这个案例是家人的电脑,已经使用多年,又是有小孩操作过的,所以电脑密码根本不记得是什么了?那难道这台电脑就废了吗?需要重新装机吗?那里面的资料不是没有了? 为了解决以上问题,一般…

MySQL 高可用架构

MySQL 是实际生产中最常用的数据库,生产环境数据量极为庞大,对性能和安全要求很高,单机的 MySQL 是远远达不到的,所以必须搭建一个主从复制架构,同时可以基于一些工具实现高可用架构,在此基础上&#xff0c…

基于Java SSM框架+Vue实现药品保健品购物网站项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架Vue实现药品保健品购物网站演示 摘要 随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 ssm药源购物网站,主要的模块包括两个用户,管理员权限:用…

Re55:读论文 Entities as Experts: Sparse Memory Access with Entity Supervision

诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文名称:Entities as Experts: Sparse Memory Access with Entity Supervision 模型名称:Entities as Experts (EaE) ArXiv网址:https://arxiv.org/abs/2004.07202 本文…

leetcode:有效的括号

题目描述 题目链接:20. 有效的括号 - 力扣(LeetCode) 题目分析 题目给了我们三种括号:()、{ }、[ ] 这里的匹配包括:顺序匹配和数量匹配 最优的思路就是用栈来解决: 括号依次入栈…

2 时间序列预测入门:GRU

0 论文地址 GRU 原论文:https://arxiv.org/pdf/1406.1078v3.pdf GRU(Gate Recurrent Unit)是循环神经网络(RNN)的一种,可以解决RNN中不能长期记忆和反向传播中的梯度等问题,与LSTM的作用类似&a…

(附源码)springboot电影售票系统小程序 计算机毕设36991

目 录 摘要 1 绪论 1.1课题目的与意义 1.2研究背景 1.3论文结构与章节安排 1.4小程序框架以及目录结构介绍 2 springboot电影售票系统小程序系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 操作可行性分析 2.2 系统流程分析 2.2.1 数据…