iOS 进阶 (四) -- 卡顿监控

引发卡顿的几种原因

  • 复杂UI、图文混排的绘制量过大
  • 在主线程上做网络同步请求
  • 在主线程做大量的IO操作
  • 运算量过大,导致CPU持续高占用
  • 列锁和主子线程抢锁

对于卡顿监控,可能第一个想到的办法就是利用CADisplayLink去检测FPS值的变化,但这种方式不能满足我们对卡顿监控的需求。例如说,对于动画片来说,它的FPS是24,远远没有达到60的满帧,但是对于动画片来说,我们已经看的是很连贯的了,感觉不出来卡顿的现像,但24帧如果出现在App上,那可想而知,几乎卡的我们想杀掉App进程了吧。
其实用户所感知的卡顿主要就是来自主线程上的卡顿,因为我们要监控解决的也是这种情况。

利用RunLoop来监控卡顿

我们都知道,线程的消息事件是依赖于NSRunLoop的,所以从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 所有状态改变
}

如果RunLoop的线程,进入睡眠前方法的执行时间过长而导致无法进入睡眠,或者线程唤醒后接收消息时间过长而无法进入下一步的话,就可以认为是线程受阻了,如果此时是在主线程上的话,表现出来的就是卡顿了。
所以,如果利用RunLoop来监控卡顿,就需要关注这两个阶段。进入睡眠之前和唤醒后的两个loop状态值,也就是kCFRunLoopBeforeSourceskCFRunLoopAfterWaiting(触发Source0回调和接收match_port消息两个状态)

监控的实现

首先需要创建一个RunLoop的观察者,方便我们检测RunLoop的状态

CFRunLoopObserverContext context = { 0, (__bridge void *)self, NULL, NULL};
runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context);
CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);

再将观察者runLoopObserver添加到主线程的RunLoopcommon模式下,然后再创建一个持续的子线程专门用来监控主线程的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
});

你可能感兴趣的:(iOS 进阶 (四) -- 卡顿监控)