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
}

複製代碼
相關文章
相關標籤/搜索