性能优化02.2:Runloop监测
RunLoop 原理
RunLoop
在iOS
里由CFRunLoop
实现。简单来说,RunLoop
是用来监听输入源,进行调度处理的。
这里的输入源可以是输入设备、网络、周期性或者延迟时间、异步回调。
RunLoop
会接收两种类型的输入源:一种是来自另一个线程或者来自不同应用的异步消息;另一种是来自预订时间或者重复间隔的同步事件。RunLoop
的目的是,当有事件要去处理时保持线程忙,当没有事件要处理时让线程进入休眠。所以,RunLoop
不光能够运用到监控卡顿上,还可以提高用户的交互体验。通过将那些繁重而不紧急会大量占用CPU
的任务(比如图片加载),放到空闲的RunLoop
模式里执行,就可以避开在UITrackingRunLoopMode
这个RunLoop
模式时执行
- RunLoop 执行流程
- 在RunLoop运行的整个过程中,
loop
的状态包括6
个:
1 | typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { |
Runloop 流程图:
注:
1、Source0
被添加到RunLoop
上时并不会主动唤醒线程,需要手动去唤醒。Source0
负责对触摸事件的处理以及performSeletor:onThread:
。
2、Source1
具备唤醒线程的能力,使用的是基于Port
的线程间通信
。Source1
负责捕获系统事件,并将事件交由Source0
处理。
1 | RunLoop 顺序: |
理清楚Runloop的
运行机制
,就很容易明白处理事件主要有两个时间段kCFRunLoopBeforeSources
发送之后和kCFRunLoopAfterWaiting
发送之后。dispatch_semaphore_t
是一个信号量机制,信号量到达、或者超时会继续向下进行,否则等待,如果超时则返回的结果必定不为0,信号量到达结果为0。利用这个特性我们判断卡顿出现的条件为
在信号量发送 kCFRunLoopBeforeSources
和kCFRunLoopAfterWaiting
后进行了大量的操作,在一段时间内没有再发送信号量,导致超时。也就是说主线程通知
状态长时间的停留在这两个状态上了
。转换为代码就是判断有没有超时,超时
了,判断当前停留的状态是不是这两个状态,如果是,就判定为卡顿
。
这样就能解释通为什么要用这两个信号量判断卡顿。这么一个简单的问题,思路转不过来就绕进去了,现在回看感觉这个很简单,也是耗了一天时间。
要利用
RunLoop
原理来监控卡顿的话,要关注两个阶段。分别是kCFRunLoopBeforeSources
和kCFRunLoopAfterWaiting
,就是要触发Source0
回调和接收mach_port
消息两个状态。
具体实现
- 创建一个 ZJRunloopMonitor 类
.h 文件
1 |
|
.m 文件
1 | #import "ZJRunloopMonitor.h" |
- 观察RunLoop 的
common
模式
将创建好的观察者
runLoopObserver
添加到主线程RunLoop
的common
模式下观察。
然后,创建一个持续的子线程专门用来监控主线程的RunLoop
状态。一旦发现进入睡眠前的
kCFRunLoopBeforeSources
状态,或者唤醒后的状态kCFRunLoopAfterWaiting
,在设置的时间阈值内一直没有变化,即可判定为卡顿
。代码中触发卡顿的时间阈值 ,设置成了
2
秒。这个2
秒的阈值合理?我们可以根据WatchDog
机制来设置。WatchDog
在不同状态下设置的不同时间,如下所示:
启动(Launch):20s;
恢复(Resume):10s;
挂起(Suspend):10s;
退出(Quit):6s;
后台(Background):3min(在 iOS 7 之前,每次申请 10min; 之后改为每次申请 3min,可连续申请,最多申请到 10min)。
- 接下来,我们就可以
log
出堆栈的信息,从而进一步分析出具体是哪个方法的执行时间过长。
如何获取卡顿的方法堆栈信息
子线程监控发现卡顿后,还需要记录当前出现卡顿的方法堆栈信息,并适时推送到服务端供开发者分析,从而解决卡顿问题。
直接调用系统函数获取堆栈
这种方法的优点在于,性能消耗小。但是,它只能够获取简单的信息,也没有办法配合
dSYM
来获取具体是哪行代码出了问题,而且能够获取的信息类型也有限。但因为性能比较好,所以适用于观察大盘统计卡顿情况,而不是想要找到卡顿原因的场景。
- 直接获取堆栈信息
1 | #include <libkern/OSAtomic.h> |
- 三方库
直接用 PLCrashReporter 这个开源的第三方库来获取堆栈信息。这种方法的特点是,能够定位到问题代码的具体位置,而且性能消耗也不大。
1 | // 获取数据 |
- Post title:性能优化02.2:Runloop监测
- Post author:张建
- Create time:2023-03-23 08:07:53
- Post link:https://redefine.ohevan.com/2023/03/23/OC性能优化/性能优化02.2:Runloop监测/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.