iOS关于RunLoop及卡顿监控

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

RunLoop的内部结构以下:async

Observer主要用来得知RunLoop不一样时期的状态,当RunLoop状态改变后会通知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、CFSockt等.这些Source0是先被标记为待处理,而后再唤醒Runloop处理. Source1是由XNU内核管理,Mach_Port来驱动使用当.触发trap内核会被唤醒,mach_msg()方法会从用户态切换到内核态,内核态中的mach_msg()会完成实际任务.oop

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

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

@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
}

复制代码
相关文章
相关标签/搜索