做网站在经营范围内属于什么,魅姬直播,网站建设应该怎么做,网页版微信文件存储路径前言
曾经在写项目的时候遇到过这么一个问题。#xff1a;
项目中添加了一个tableview#xff0c;然后还有一个计时器#xff0c;当滑动tableview的时候会阻塞计时器#xff0c;你得执行这么一段代码后#xff0c;计时器才能正常运行。
RunLoop.current.add(timer, for…前言
曾经在写项目的时候遇到过这么一个问题。
项目中添加了一个tableview然后还有一个计时器当滑动tableview的时候会阻塞计时器你得执行这么一段代码后计时器才能正常运行。
RunLoop.current.add(timer, forMode: .common)
发生这种情况是因为我在defaultRunLoopMode上隐式创建计时器这实际上是我们应用程序的主线程。然后当用户积极与我们的用户界面互动时这将暂停然后在他们停止时重新激活。
什么是runloop呢
RunLoop是一个事件循环机制用于管理线程中的事件和消息。它允许线程在没有任务的情况下休眠并在有任务需要处理时唤醒线程。 RunLoop主要负责以下几个方面 处理输入源RunLoop负责处理输入源包括用户界面事件、触摸事件、定时器事件、网络事件等通过RunLoop能够有效地处理这些事件让应用程序的响应更加及时。 保持线程活动线程保活RunLoop能够保持线程活动即使在没有任务时RunLoop也会让线程休眠而不会退出以便随时处理来自输入源的事件。 定时器功能RunLoop提供了一些定时器功能例如延迟执行和重复执行某个任务。通过定时器功能可以很方便地在指定时间执行任务。 优化性能RunLoop能够优化应用程序的性能通过RunLoop能够让应用程序在有任务需要处理时及时唤醒线程而在没有任务时让线程休眠从而避免了线程的空转减少了CPU的占用提高了应用程序的性能。 runloop和线程之间的关系
每条线程都有唯一的一个与之对应的RunLoop对象RunLoop保存在一个全局的Dictionary里线程作为keyRunLoop作为value线程刚创建时并没有RunLoop对象RunLoop会在第一次获取它时创建RunLoop会在线程结束时销毁主线程的RunLoop已经自动获取创建用于响应UI和用户交互事件子线程默认没有开启RunLoop runloop的基本组成
Input Source输入源: 异步事件的通道如用户交互手势、触摸、网络请求等。 Timer Source定时器源: 定时任务如 NSTimer它会定期触发某个操作。RunLoop 会定期检查定时器并在时间到达时触发相应的操作。 Observer观察者: 监听 RunLoop 状态的变化如启动、休眠、唤醒、退出等。可以用于监控 RunLoop 的执行过程便于调试或优化。 以下是Observer观察者的常见状态
kCFRunloopEntry (runloop准备启动)kCFRunloopBeforeTimers (通知观察者,runloop将要对Timer的一些相关事件进行处理了)kCFRunloopBeforeSources (将要处理一些Sources事件)kCFRunloopBeforeWaiting( 即将要发生用户态到内核态的切换 用户态 — 内核态)没事做进入内核态避免资源浪费kCFRunloopAfterWaiting (内核态—转—用户态)kCFRunloopExit runloop退出通知 简单来说就是runloop从用户态切换到内核态可以节省系统资源使得线程在没有任务时不会浪费 CPU 时间。
RunLoop 通过监听输入源、定时器源、观察者来处理和调度事件确保线程可以在处理完事件后及时进入休眠。 runloop的不同模式
runloop可以根据所需切换成不同的模式
NSDefaultRunLoopMode 默认模式处理大多数应用事件如 Timer定时器触发、网络事件等。UITrackingRunLoopMode UI模式专门处理UI事件NSRunLoopCommonModes 常用模式允许 RunLoop 同时处理多种事件类型。使用场景包括当用户滚动 UIScrollView 时仍能处理定时器事件。UIInitializationRunLoopMode 在刚启动App时第进入的第一个Mode启动完成后就不再使用GSEventReceiveRunLoopMode 接受系统事件的内部Mode
示例
let timer Timer(timeInterval: 1.0, repeats: true) { _ inprint(Timer fired during scroll)
}
//将定时器添加到 .common 模式中确保在滚动时仍能触发
RunLoop.current.add(timer, forMode: .common) 将Timer定时器添加到.common模式中才能在ScrollView滚动时响应的原理 当应用处于空闲状态时RunLoop 处于 NSDefaultRunLoopMode默认模式它处理正常任务包括 Timer 的回调、用户输入等。当用户与 ScrollView 交互时RunLoop 切换到 UITrackingRunLoopModeUI模式在这种模式下RunLoop 只处理与用户交互相关的任务确保滑动的流畅性。此时未被标记为 “Common” 的 Timer 和其他事件源会被暂时忽略。如果 Timer 只添加到 NSDefaultRunLoopMode默认模式滑动时UITrackingRunLoopMode它将不会触发直到滑动结束后 RunLoop 切换回 DefaultModeTimer才会被继续触发。使用 NSRunLoopCommonModes常用模式 可以让 Timer 在所有标记为 “Common” 的模式下执行保证用户滑动时依然能触发回调。 tipsCommon模式默认将runloop添加到NSDefaultRunLoopMode和UITrackingRunLoopMode模式。 runloop的循环流程
检查并处理输入源事件。检查并处理定时器事件。如果没有任务进入休眠等待新的事件。当有事件到来时唤醒并处理事件。重复以上过程直到 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。
举个例子 我们触摸屏幕屏幕表面的事件会先包装成EventEvent先告诉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?tabreadme-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?tabreadme-ov-file#3-runloop
Runloop解析_objective-c runloop-CSDN博客
RunLoop in details | kyryl horbushko