美文网首页iOS常用
13 | 如何利用 RunLoop 原理去监控卡顿?

13 | 如何利用 RunLoop 原理去监控卡顿?

作者: X先生_未知数的X | 来源:发表于2020-07-06 23:55 被阅读0次

    卡顿问题,就是在主线程上无法响应用户交互的问题。
    现在,我们先来看一下导致卡顿问题的几种原因:
    1.复杂 UI 、图文混排的绘制量过大;
    2.在主线程上做网络同步请求;
    3.在主线程做大量的IO 操作;
    4.运算量过大,CPU持续高占用;
    5.死锁和主子线程抢锁。

    RunLoop 原理

    对于iOS开发来说,监控卡顿就是要去找到主线程上都做了哪些事儿。我们都知道,线程的消息事件是依赖于NSRunLoop 的,所以从NSRunLoop入手,就可以知道主线程上都调用了哪些方法。我们通过监听 NSRunLoop 的状态,就能够发现调用方法是否执行时间过长,从而判断出是否会出现卡顿。
    所以,我推荐的监控卡顿的方案是:通过监控 RunLoop 的状态来判断是否会出现卡顿。
    RunLoop 的目的是,当有事件要去处理时保持线程忙,当没有事件要处理时让线程进入休眠。

    如何检查卡顿?

    要想监听 RunLoop,你就首先需要创建一个 CFRunLoopObserverContext 观察者,代码如下:

    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
    runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0,&runLoopObserverCallBack,&context);
    将创建好的观察者 runLoopObserver 添加到主线程 RunLoop 的 common 模式下观察。然后,创建一个持续的子线程专门用来监控主线程的 RunLoop 状态。

    一旦发现进入睡眠前的 kCFRunLoopBeforeSources 状态,或者唤醒后的状态 kCFRunLoopAfterWaiting,在设置的时间阈值内一直没有变化,即可判定为卡顿。接下来,我们就可以 dump 出堆栈的信息,从而进一步分析出具体是哪个方法的执行时间过长。

    开启一个子线程监控的代码如下:

    //创建子线程监控

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //子线程开启一个持续的 loop 用来进行监控
        while (YES) {
            long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC));
            if (semaphoreWait != 0) {
                if (!runLoopObserver) {
                    timeoutCount = 0;
                    dispatchSemaphore = 0;
                    runLoopActivity = 0;
                    return;
                }
                //BeforeSources 和 AfterWaiting 这两个状态能够检测到是否卡顿
                if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
                    //将堆栈信息上报服务器的代码放到这里
                } //end activity
            }// end semaphore wait
            timeoutCount = 0;
        }// end while
    });
    

    代码中的 NSEC_PER_SEC,代表的是触发卡顿的时间阈值,单位是秒。可以看到,我们把这个阈值设置成了3秒。那么,这个3秒的阈值是从何而来呢?这样设置合理吗?其实,触发卡顿的时间阈值,我们可以根据 WatchDog 机制来设置。WatchDog 在不同状态下设置的不同时间,如下所示:


    image.png

    通过WatchDog 设置的时间,我认为可以把启动的阈值设置为10秒,其他状态则都默认设置为3秒。总的原则就是,要小于 WatchDog的限制时间。当然了,这个阈值也不用小得太多,原则就是要优先解决用户感知最明显的体验问题。

    相关文章

      网友评论

        本文标题:13 | 如何利用 RunLoop 原理去监控卡顿?

        本文链接:https://www.haomeiwen.com/subject/zavnqktx.html