利用RunLoop监测卡顿

栏目: IOS · 发布时间: 5年前

内容简介:最近学习戴铭大神的课程,其中一篇文章介绍了如何利用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 ,则认为卡顿。

步骤如下:

  1. 创建一个RunLoop的观察者(CFRunLoopObserverRef)
  2. 把观察者加入主线程的kCFRunLoopCommonModes模式中,以监测主线程
  3. 创建一个子线程来维护观察者
  4. 根据主线程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 ,则判定为卡顿。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms and Theory of Computation Handbook

Algorithms and Theory of Computation Handbook

Mikhail J. Atallah (Editor) / CRC-Press / 1998-09-30 / USD 94.95

Book Description This comprehensive compendium of algorithms and data structures covers many theoretical issues from a practical perspective. Chapters include information on finite precision issues......一起来看看 《Algorithms and Theory of Computation Handbook》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具