先來聊聊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
}
複製代碼