内容简介:最近学习戴铭大神的课程,其中一篇文章介绍了如何利用RunLoop监测卡顿,在此做个记录。文中介绍到:RunLoop是用来监听输入源,进行调度处理的。如果RunLoop的线程进入睡眠前方法的执行时间过长而导致无法进入睡眠,或者线程唤醒后接收消息时间过长而无法进入下一步,就可以认为是线程受阻了。如果这个线程是主线程的话,表现出来的就是出现了卡顿。
最近学习戴铭大神的课程,其中一篇文章介绍了如何利用RunLoop监测卡顿,在此做个记录。
一、监测卡顿的原理
文中介绍到:
RunLoop是用来监听输入源,进行调度处理的。如果RunLoop的线程进入睡眠前方法的执行时间过长而导致无法进入睡眠,或者线程唤醒后接收消息时间过长而无法进入下一步,就可以认为是线程受阻了。如果这个线程是主线程的话,表现出来的就是出现了卡顿。
RunLoop有以下几种状态:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry , // 进入 loop kCFRunLoopBeforeTimers , // 触发 Timer 回调 kCFRunLoopBeforeSources , // 触发 Source0 回调 kCFRunLoopBeforeWaiting , // 等待 mach_port 消息 kCFRunLoopAfterWaiting , // 接收 mach_port 消息 kCFRunLoopExit , // 退出 loop kCFRunLoopAllActivities // loop 所有状态改变 } 复制代码
而文中提到的2种线程受阻情况,它们的状态分别是:kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting。
二、思路
根据原理,可以得到一个监测卡顿的思路:
监测主线程RunLoop的状态,如果状态在一定时长内都是 kCFRunLoopBeforeSources
或者 kCFRunLoopAfterWaiting
,则认为卡顿。
步骤如下:
- 创建一个RunLoop的观察者(CFRunLoopObserverRef)
- 把观察者加入主线程的kCFRunLoopCommonModes模式中,以监测主线程
- 创建一个子线程来维护观察者
- 根据主线程RunLoop的状态来判断是否卡顿
三、实现方法
1. 实现代码
// 属性 { int timeoutCount; CFRunLoopObserverRef runLoopObserver; @public dispatch_semaphore_t dispatchSemaphore; CFRunLoopActivity runLoopActivity; } // 开始监测 - (void)beginMonitor { if (runLoopObserver) { return; } // dispatchSemaphore的知识参考:https://www.jianshu.com/p/24ffa819379c // 初始化信号量,值为0 dispatchSemaphore = dispatch_semaphore_create(0); //Dispatch Semaphore保证同步 //创建一个观察者 CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context); //将观察者添加到主线程runloop的common模式下的观察中 CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes); //创建子线程监控 dispatch_async(dispatch_get_global_queue(0, 0), ^{ //子线程开启一个持续的loop用来进行监控 while (YES) { // 等待信号量:如果信号量是0,则阻塞当前线程;如果信号量大于0,则此函数会把信号量-1,继续执行线程。此处超时时间设为20毫秒。 // 返回值:如果线程是唤醒的,则返回非0,否则返回0 long semaphoreWait = dispatch_semaphore_wait(self->dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 20*NSEC_PER_MSEC)); // NSLog(@"%@",@(semaphoreWait)); if (semaphoreWait != 0) { if (!self->runLoopObserver) {// observer创建失败,直接返回 self->timeoutCount = 0; self->dispatchSemaphore = 0; self->runLoopActivity = 0; return; } // 如果 RunLoop 的线程,进入睡眠前方法的执行时间过长而导致无法进入睡眠(kCFRunLoopBeforeSources),或者线程唤醒后接收消息时间过长(kCFRunLoopAfterWaiting)而无法进入下一步的话,就可以认为是线程受阻。 //两个runloop的状态,BeforeSources和AfterWaiting这两个状态区间时间能够监测到是否卡顿 if (self->runLoopActivity == kCFRunLoopBeforeSources || self->runLoopActivity == kCFRunLoopAfterWaiting) { NSLog(@"runloop状态:%@",@(self->runLoopActivity)); // 60毫秒内一直保持其中一种状态,说明卡顿(20毫秒测1次,共3次) if (++self->timeoutCount < 3) { continue; } NSLog(@"卡顿..."); } } self->timeoutCount = 0; } }); } static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { // NSLog(@"监测到线程主线程有变化,信号量+1"); SMLagMonitor *lagMonitor = (__bridge SMLagMonitor*)info; lagMonitor->runLoopActivity = activity; dispatch_semaphore_t semaphore = lagMonitor->dispatchSemaphore; // 让信号量+1。 dispatch_semaphore_signal(semaphore); } 复制代码
2. 流程说明:
为了保证子线程的同步监测,刚开始创建一个信号量是0的 dispatch_semaphore
。当监测到主线程的RunLoop,触发回调:
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { 复制代码
在回调里面发送信号,使信号量+1,值变为1:
dispatch_semaphore_signal(semaphore) 复制代码
当 dispatch_semaphore_wait
接收到信号量不为0,会返回一个不为0的值(semaphoreWait),并把信号量-1:
long semaphoreWait = dispatch_semaphore_wait(self->dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 20*NSEC_PER_MSEC)); 复制代码
然后触发一次监测功能,记录RunLoop状态。此时信号量是0,继续等待下一个信号量。
如果连续监测主线程RunLoop的状态3次,得到的结果都是 kCFRunLoopBeforeSources
或者 kCFRunLoopAfterWaiting
,则判定为卡顿。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 如何利用机器学习进行异常检测和状态监测
- 利用树莓派3和RTL-SDR V3搭建一个低成本的QRP小功率监测点
- Flutter异常监测与上报
- 视频演讲: 移动 App 性能监测
- Linux 系统监测工具 sysstat 介绍
- 监测防护SambaCry CVE-2017-7494漏洞
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。