iOS监控卡顿

监控FPS

FPS :Frames Per Second 的简称缩写,意思是每秒传输帧数,FPS值越低就越卡顿,所以这个值在一定程度上可以衡量应用在图像绘制渲染处理时的性能。iOS系统中正常的屏幕刷新率为60Hz(60次每秒)。
通过CADisplayLink实现FPS监控,CADisplayLink可以以屏幕刷新的频率调用指定selector,也就是说每次屏幕刷新的时候就调用selector,那么只要在selector方法里面统计每秒这个方法执行的次数,通过次数/时间就可以得出当前屏幕的刷新率了。
可通过YYFPSLabel或者KMCGeigerCounter进行监控,但是前者比较轻量级。
YYFPSLabel
KMCGeigerCounter

通过RunLoop监控检查卡顿

RunLoop监控原理:通过RunLoop知道主线程上都调用了哪些方法,通过监听 nsrunloop 的状态,知道调用方法是否执行时间过长,从而判断出是否卡顿。
RunLoop 原理来监控卡顿的话,就是要关注这两个阶段。RunLoop 在进入睡眠之前和唤醒后的两个 loop状态定义的值分别是 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting,也就是要触发 Sources回调和接收mach_port消息两个状态。

监听RunLoop
  • 1.需要创建一个CFRunLoopObserverContext观察者,然后将观察者runLoopObserver添加到主线程 RunLoop的common模式下观察
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0,&runLoopObserverCallBack,&context);
  • 2.创建一个持续的子线程专门用来监控主线程的RunLoop状态
// 创建子线程监控
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; // dispatch_semaphore_t 信号量
                runLoopActivity = 0;  // CFRunLoopActivity RunLoop原始状态kCFRunLoopEntry
                return;
            }
            //kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting这两个状态能够检测到是否卡顿
            if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
                // 将堆栈信息上报服务器的代码放到这里,包括timeoutCount操作
            } 
        }
        timeoutCount = 0;
    }
});

NSEC_PER_SEC代表的是触发卡顿的时间阈值,单位是秒。可以看到,我们把这个阀值设置成了 3 秒。

获取卡顿的方法堆栈信息

方法1:直接调用系统函数方法的主要思路是:用 signal 进行错误信息的获取
性能消耗小。但是,它只能够获取简单的信息,也没有办法配合 dSYM 来获取具体是哪行代码出了问题,而且能够获取的信息类型也有限。适用于观察大盘统计卡顿情况、而不是想要找到卡顿原因的场景。

static int s_fatal_signals[] = {
    SIGABRT,
    SIGBUS,
    SIGFPE,
    SIGILL,
    SIGSEGV,
    SIGTRAP,
    SIGTERM,
    SIGKILL,
};

static int s_fatal_signal_num = sizeof(s_fatal_signals) / sizeof(s_fatal_signals[0]);

void UncaughtExceptionHandler(NSException *exception) {
    NSArray *exceptionArray = [exception callStackSymbols]; // 得到当前调用栈信息
    NSString *exceptionReason = [exception reason];       // 非常重要,就是崩溃的原因
    NSString *exceptionName = [exception name];           // 异常类型
}

void SignalHandler(int code)
{
    NSLog(@"signal handler = %d",code);
}

void InitCrashReport()
{
    // 系统错误信号捕获
    for (int i = 0; i < s_fatal_signal_num; ++i) {
        signal(s_fatal_signals[i], SignalHandler);
    }
    
    //oc 未捕获异常的捕获
    NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        InitCrashReport();
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

方法2:利用PLCrashReporter
能够定位到问题代码的具体位置,而且性能消耗也不大

NSData *lagData = [[[PLCrashReporter alloc]
                                          initWithConfiguration:[[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]] generateLiveReport];
PLCrashReport *lagReport = [[PLCrashReport alloc] initWithData:lagData error:NULL];
NSString *lagReportString = [PLCrashReportTextFormatter stringValueForCrashReport:lagReport withTextFormat:PLCrashReportTextFormatiOS];
NSLog(@"lag happen, detail below: \n %@",lagReportString);

推荐阅读更多精彩内容

  • iOS刨根问底-深入理解RunLoop 2017-05-08 10:35 by KenshinCui 概述 Run...
    mengjz阅读 1,238评论 1 10
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 9,259评论 1 26
  • iOS刨根问底-深入理解RunLoop 概述 RunLoop作为iOS中一个基础组件和线程有着千丝万缕的关系,同时...
    reallychao阅读 602评论 0 6
  • 概述 RunLoop作为iOS中一个基础组件和线程有着千丝万缕的关系,同时也是很多常见技术的幕后功臣。尽管在平时多...
    sumrain_cloud阅读 661评论 0 5
  • 暗恋是什么? 就是对另一个人心存爱意或好感,因为种种原因这种爱意没法告白。通常所爱的对象并没有这样的爱意或好感,很...
    稳_97fb阅读 379评论 2 5
  • 白昼与黑夜的交替, 时间消逝,悄无声息。 我攥紧回忆,渡寸寸光阴,在轮回中找寻你。 做了无数次与你相逢的美梦, 梦...
    牛大碗C阅读 518评论 0 6
  • 01. 原来有些事,需要自己经历才知道。就好比小马过河,只有自己试过,才知道水的深浅。 02.人的潜意识里总爱比较...
    倚窗风景阅读 45评论 0 0
  • 战火的遗物,黑帮的最爱│横须贺夹克 转载自 EYEE蜂潮 在上期“不良少年的时尚法则”中小编我故意没提横须贺夹克这...
    gagami阅读 981评论 0 1
  • 顾春阳 打卡第419天 【知~学习】 《六项精进》5遍 共1642遍 《大学》5遍 共1653遍 【经典名句分享】...
    顾春阳阅读 33评论 0 0