如何利用 RunLoop 监控 app 卡顿

本文意义在分析如何利用runloop监控卡顿。代码可以看戴铭大佬的代码

卡顿问题的几种原因

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

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 真正处理事务的状态区间是
KCFRunloopBeforeSources->KCFRunLoopBeforeWaiting
kCFRunLoopAfterWaiting-> kCFRunLoopBeforeTimers

所以我们监听 RunLoop 在进入睡眠之前和唤醒之后的两个状态,分别是 kCFRunLoopBeforeSourceskCFRunLoopAfterWaiting
也就是要触发 Source0 回调和接收 mach_port 消息两个状态。

大致思路
  • 创建一个 CFRunLoopObserverContext 观察者
  • 将创建好的观察者 runLoopObserver 添加到主线程 RunLoop 的 common 模式下观察
  • 每当监听到 Observer 通知,使信号量的值 +1
  • 创建一个持续的子线程使用信号量专门用来监控主线程的 RunLoop 状态,设置信号量的等待时间
  • 如过等待时间内子线程还没有被唤醒,则认为发生了卡顿

上代码 :

#import 

@interface SMLagMonitor : NSObject

+ (instancetype)shareInstance;

- (void)beginMonitor; //开始监视卡顿
- (void)endMonitor;   //停止监视卡顿

@end
#import "SMLagMonitor.h"

@interface SMLagMonitor() {
    int timeoutCount;
    CFRunLoopObserverRef runLoopObserver;
    @public
    dispatch_semaphore_t dispatchSemaphore;
    CFRunLoopActivity runLoopActivity;
}
@property (nonatomic, strong) NSTimer *cpuMonitorTimer;
@end

@implementation SMLagMonitor

#pragma mark - Interface
+ (instancetype)shareInstance {
    static id instance = nil;
    static dispatch_once_t dispatchOnce;
    dispatch_once(&dispatchOnce, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

- (void)beginMonitor {
    //监测卡顿
    if (runLoopObserver) {
        return;
    }
    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) {
            long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));
            //  semaphoreWait 的值不为 0, 说明线程被堵塞
            if (semaphoreWait != 0) {
                if (!runLoopObserver) {
                    timeoutCount = 0;
                    dispatchSemaphore = 0;
                    runLoopActivity = 0;
                    return;
                }
                // BeforeSources和 AfterWaiting 这两个 runloop 状态的区间时间能够检测到是否卡顿
                if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
                    // 将堆栈信息上报服务器的代码放到这里
                    if (++ timeoutCount < 5) { //连续5次就是250毫秒
                        continue;
                    } else {
                        NSLog(@"卡顿了");
                    }
                } //end activity
            }// end semaphore wait
            timeoutCount = 0;
        }// end while
    });
    
}

- (void)endMonitor {
    [self.cpuMonitorTimer invalidate];
    if (!runLoopObserver) {
        return;
    }
    CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
    CFRelease(runLoopObserver);
    runLoopObserver = NULL;
}

#pragma mark - Private

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
    SMLagMonitor *lagMonitor = (__bridge SMLagMonitor*)info;
    lagMonitor->runLoopActivity = activity;
    
    dispatch_semaphore_t semaphore = lagMonitor->dispatchSemaphore;
    dispatch_semaphore_signal(semaphore);
}

@end

你可能感兴趣的:(如何利用 RunLoop 监控 app 卡顿)