利用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 ,则判定为卡顿。


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

查看所有标签

猜你喜欢:

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

雷军

雷军

蔡艳鹏 / 2012-12 / 29.80元

《雷军:人因梦想而伟大》内容简介:人生充满着期待,梦想连接着未来。雷军一直有个梦,就是建一个受世人尊敬的企业。他不仅建立了属于自己的受人尊敬的企业,也在帮助别人实现心中的梦想。雷军可以说是创业者、职场人奋斗的榜样,从他在金山的不折不挠,在投资界的百投百中,到小米的成功……无不充满传奇,让无数人争相效仿。一起来看看 《雷军》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具