本文意義在分析如何利用runloop監(jiān)控卡頓近她。代碼可以看戴銘大佬的代碼
卡頓問題的幾種原因
- 復(fù)雜 UI 、圖文混排的繪制量過大
- 在主線程上做網(wǎng)絡(luò)同步請求
- 在主線程做大量的 IO 操作
- 運(yùn)算量過大,CPU 持續(xù)高占用
- 死鎖和主子線程搶鎖
Runloop 監(jiān)控卡頓
runloop 工作流程
runloop 工作流程
首先明確了 Runloop的狀態(tài)有六個(gè)
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry , // 進(jìn)入 loop
kCFRunLoopBeforeTimers , // 觸發(fā) Timer 回調(diào)
kCFRunLoopBeforeSources , // 觸發(fā) Source0 回調(diào)
kCFRunLoopBeforeWaiting , // 等待 mach_port 消息
kCFRunLoopAfterWaiting ), // 接收 mach_port 消息
kCFRunLoopExit , // 退出 loop
kCFRunLoopAllActivities // loop 所有狀態(tài)改變
}
上圖可以看出
Runloop 真正處理事務(wù)的狀態(tài)區(qū)間是
KCFRunloopBeforeSources->KCFRunLoopBeforeWaiting
kCFRunLoopAfterWaiting-> kCFRunLoopBeforeTimers
所以我們監(jiān)聽 RunLoop 在進(jìn)入睡眠之前和喚醒之后的兩個(gè)狀態(tài)掺喻,分別是 kCFRunLoopBeforeSources
和 kCFRunLoopAfterWaiting
也就是要觸發(fā) Source0 回調(diào)和接收 mach_port 消息兩個(gè)狀態(tài)。
大致思路
- 創(chuàng)建一個(gè) CFRunLoopObserverContext 觀察者
- 將創(chuàng)建好的觀察者 runLoopObserver 添加到主線程 RunLoop 的 common 模式下觀察
- 每當(dāng)監(jiān)聽到 Observer 通知,使信號(hào)量的值 +1
- 創(chuàng)建一個(gè)持續(xù)的子線程使用信號(hào)量專門用來監(jiān)控主線程的 RunLoop 狀態(tài)殉挽,設(shè)置信號(hào)量的等待時(shí)間
- 如過等待時(shí)間內(nèi)子線程還沒有被喚醒,則認(rèn)為發(fā)生了卡頓
上代碼 :
#import <Foundation/Foundation.h>
@interface SMLagMonitor : NSObject
+ (instancetype)shareInstance;
- (void)beginMonitor; //開始監(jiān)視卡頓
- (void)endMonitor; //停止監(jiān)視卡頓
@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 {
//監(jiān)測卡頓
if (runLoopObserver) {
return;
}
dispatchSemaphore = dispatch_semaphore_create(0); //Dispatch Semaphore保證同步
//創(chuàng)建一個(gè)觀察者
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
0,
&runLoopObserverCallBack,
&context);
//將觀察者添加到主線程runloop的common模式下的觀察中
CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
//創(chuàng)建子線程監(jiān)控
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//子線程開啟一個(gè)持續(xù)的loop用來進(jìn)行監(jiān)控
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 這兩個(gè) runloop 狀態(tài)的區(qū)間時(shí)間能夠檢測到是否卡頓
if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
// 將堆棧信息上報(bào)服務(wù)器的代碼放到這里
if (++ timeoutCount < 5) { //連續(xù)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