导致卡顿的原因
- 复杂 UI 、图文混排的绘制量过大
 
- 在主线程上做网络同步请求
 
- 在主线程做大量的 IO 操作
 
- 运算量过大,CPU 持续高占用
 
- 死锁和主子线程抢锁
 
原理
监控卡顿就是找到主线程都做了哪些事。线程的消息事件是依赖于 NSRunLoop 的。我们通过监听 NSRunLoop 的状态,能够发现调用方法是否执行事件过长,从而判断是否会出现卡顿。
RunLoop 会接受到两种输入源,一种是来自另一个线程或者来自不同应用到异步消息,另一种是来自预定时间或者重复间隔到同步事件。RunLoop 的目的是,当有事件要去处理时保持线程忙,当没有事件要处理时让线程进入休眠。
Runloop 的六个状态
1 2 3 4 5 6 7 8 9
   | typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {     kCFRunLoopEntry ,      kCFRunLoopBeforeTimers ,      kCFRunLoopBeforeSources ,      kCFRunLoopBeforeWaiting ,      kCFRunLoopAfterWaiting ),      kCFRunLoopExit ,      kCFRunLoopAllActivities   }
  | 
 
如果 RunLoop 的线程,进入睡眠前方法的执行时间过长而导致无法进入睡眠,或者睡眠唤醒后接受消息时间过长而无法进入下一步的话,就可以认为是线程受阻了。如歌这个线程主线程的话,表现出来的就是出现了卡顿。
所以需要关注两个阶段。RunLoop 在进入睡眠前和唤醒后的两个 loop 状态定义的值,分别是 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting,也就是触发 Source0 回调和接受 mach_port 消息两个状态。
具体实现
创建观察者 CFRunLoopObserverContext
1 2
   | CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0,&runLoopObserverCallBack,&context);
  | 
 
将观察者 runLoopObserver 添加到主线程 RunLoop 到 common 模式下观察。然后创建一个持续到子线程专门用来监控主线程到 RunnLoop 状态。
一旦发现进入睡眠前的 kCFRunLoopBeforeSources 状态,或者睡眠后的状态 kCFRunLoopAfterWaiting,在设置的时间阈值内一直没有变化,即可判定为卡顿。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   |  dispatch_async(dispatch_get_global_queue(0, 0), ^{          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;             }                          if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {                              }          }         timeoutCount = 0;     } });
 
  | 
 
获取卡顿的方法堆栈信息
直接调用系统函数
优点:性能消耗小。但只能获取简单但信息,也没办法配合 dSYM 来获取具体哪行代码出了问题,而且能够获取的信息类型也有限。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
   |  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);     }
           NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler); }
  int main(int argc, char * argv[]) {     @autoreleasepool {         InitCrashReport();         return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
 
  | 
 
使用 PLCrashReporter 开源第三方库获取堆栈信息
能定位到具体代码,性能消耗也不大。
1 2 3 4 5 6 7 8 9
   |  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);
 
  |