iOS关于RunLoop及卡顿监控

2,182 阅读4分钟

先来聊聊RunLoop,Runloop是一个接收处理消息的对象,它通过入口函数来启动执行Event loop模型逻辑.RunloopiOS中只能在当前线程中获取,不能手动创建(主线程的RunLoop可以在子线程中获取).获取的时候系统会自动创建.

RunLoop的内部结构如下:

Observer主要用来得知RunLoop不同时期的状态,当RunLoop状态改变后会通知Observer,Observer回执行回调.我们获取或者知道这些状态什么时候触发,可以去做很多操作和优化.

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry , // 进入 loop
    kCFRunLoopBeforeTimers , // 即将触发 Timer 回调
    kCFRunLoopBeforeSources , // 即将触发 Source0 回调
    kCFRunLoopBeforeWaiting , // 等待 mach_port 消息,进入休眠
    kCFRunLoopAfterWaiting ), // 唤醒,接收 mach_port 消息
    kCFRunLoopExit , // 退出 loop
    kCFRunLoopAllActivities  // loop 所有状态改变
}

Source是输入源,其实就是处理事件的回调.它有两个版本:一个是Source0,一个是Source1.其中Source0是非基于端口的输入源,主要用于处理内部事件,App自己管理的事件,比如:UIEvent、UITouch等(其实是先触发source1,source1的回调再触发source0,source0通过UIApplication队列分发事件).可以自定义,需手动唤醒,自定义输入源是先被标记为待处理,然后再唤醒Runloop处理. Source1是由XNU内核管理,Mach_Port来驱动使用当.触发trap内核会被唤醒,mach_msg()方法会从用户态切换到内核态,内核态中的mach_msg()会完成实际任务.

Timer就是定时相关的了,比如NSTimer就会把不同的时间点注册到RunLoop中,定时唤醒Port,处理消息.

Runloop内部逻辑:

  • 1、启动runloop;
  • //进入do-while循环.{
  • 2、通知observer即将启动定时器;
  • 3、通知observer即将启非基于端口的输入源;
  • 4、启动非基于端口的输入源;
  • 5、如果有基于端口的输入源准备就绪,就跳转到9处理消息;
  • 6、通知observer即将进入休眠;
  • 7、休眠等待唤醒;
  • 8、通知observer即将被唤醒;
  • 9、处理消息事件,跳回2;
  • }
  • 10、退出runloop;

下面是通过监控主线程RunLoop监控卡顿的代码,及详细注释:

@interface MCXLagMonitor(){
    CFRunLoopObserverRef runLoopObserver;
    
    int timeoutCounting;//超时计数

    dispatch_semaphore_t dispatchSemaphore; //信号量
    CFRunLoopActivity runLoopActivity; //RunLoop的状态
}

@end

@implementation MCXLagMonitor

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

- (void)beginMonitor{
    CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL}; //context是一个结构体,
    第二个info参数会传到CFRunLoopObserverCreate的callout的info中.
    
    dispatchSemaphore = dispatch_semaphore_create(0);//创建信号量
    
    runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context);//参数分别是: 分配空间 状态枚举 是否循环调用observer 优先级 回调函数 结构体
    
    CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);//添加到主线程的RunLoop
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{//开启监控子线程
        
        while (YES) {//loop
            
            long semphoreWait = dispatch_semaphore_wait(self->dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC));//信号量为0的时候会等待1秒再执行后面的代码,即1秒为超时时间.如果信号量为0等待超时后该方法就返回非零,否则返回0,也就是说信号量在观察到RunLoop变化的时候会执行callout信号量+1,然后该方法-1返回0,继续执行下面方法.另外,1秒timeout的阻塞过程中,如果信号量因状态改变增量,就直接返回0执行后面代码.
            
            if (semphoreWait == 0) {
                self->timeoutCounting = 0;
            }else{
                if (!self->runLoopObserver) {
                    self->dispatchSemaphore = 0;
                    self->timeoutCounting = 0;
                    self->runLoopActivity = 0;
                }
                
                if (self->runLoopActivity == kCFRunLoopBeforeSources || self->runLoopActivity == kCFRunLoopAfterWaiting) {//RunLoop两个状态,如果触发即将进入source0状态后一直没有进入下一个BeforeWaiting状态,那说明方法执行时间过长. 然后是AfterWaiting也就是即将唤醒状态,如果这个状态持续时间过久,说明调用mach_msg 等待接受mach_port的消息时间过长而无法进入下一状态.他们的表现就是阻塞主线程,造成卡顿,通过监控它们来监控卡顿.
                    
                    if (++self->timeoutCounting<3) {//超过3s上报堆栈信息 如果觉得长的话,可以把上面的时间改成纳秒、毫秒等
                        continue;
                    }
                    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{//再开启一个子线程来上报堆栈信息
                        NSLog(@"堆栈信息");
                    });
                }
                
            }
            
        }
        
    });
    
}

- (void)endMonitor{
    if (!runLoopObserver) {
        return;
    }
    CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
    CFRelease(runLoopObserver);
    runLoopObserver = NULL;
}

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
    MCXLagMonitor *monitor = (__bridge MCXLagMonitor *)(info);//桥接self
    monitor->runLoopActivity = activity;
    dispatch_semaphore_signal(monitor->dispatchSemaphore);//信号量+1
}