筆記(寫在前面): 關於應用的性能監控,須要從多方面進行綜合考慮,此處僅從其中一個方面,進行學習研究。
如何判斷主線程卡頓:
監測NSRunLoop耗時狀況。服務器
NSRunLoop的調用主要在
kCFRunLoopBeforeSources
和kCFRunLoopBeforeWaiting
之間,以及kCFRunLoopAfterWaiting
以後。所以,如果發現這個兩個時間內耗時過長,就能夠斷定此時主線程出現卡頓狀況。async
使用CFRunLoopObserverRef,實時得到這些狀態值的變化,以下:函數
/// RunLoop狀態觀察回調 static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { <#MyClass#> *object = (__bridge <#MyClass#>*)info; // 記錄狀態值 object->activity = activity; }
/// 註冊RunLoop狀態觀察 - (void)registerRunLoopObserver { CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context); CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); }
另外開啓一個線程,實時計算兩個狀態區域之間的耗時,是否達到閾值。
dispatch_semaphore_t
讓子線程更及時地獲知主線程NSRunLoop狀態變化oop卡頓覆蓋範圍:
屢次連續小卡頓
、單次長時間卡頓
性能
添加計算邏輯,以下:學習
/// RunLoop狀態觀察回調 static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { <#MyClass#> *object = (__bridge <#MyClass#>*)info; // 記錄狀態值 object->activity = activity; // 發送信號 dispatch_semaphore_t semaphore = object->semaphore; dispatch_semaphore_signal(semaphore); }
/// 註冊RunLoop狀態觀察,並計算是否卡頓 - (void) registerRunLoopObserver { CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context); CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); // 建立信號 semaphore = dispatch_semaphore_create(0); // 在子線程監控時長 dispatch_async(dispatch_get_global_queue(0, 0), ^{ while (YES) { // 假定連續5次超時50ms認爲卡頓(固然也包含了單次超時250ms) long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC)); if (st != 0) { if (activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting) { if (++timeoutCount < 5) { continue; } // 發現卡頓 NSLog(@"卡、卡、卡、頓、頓、了"); } } timeoutCount = 0; } }); }
目睹卡頓現場,記錄此時的調用函數信息,做爲卡頓證據。
此處,使用第三方Crash收集組件PLCrashReporter
,它不只能夠收集Crash信息,也可用於實時獲取各線程的調用堆棧,使用示例以下:測試
PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]; PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config]; NSData *data = [crashReporter generateLiveReport]; PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL]; NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter withTextFormat:PLCrashReportTextFormatiOS]; NSLog(@"------------\n%@\n------------", report);
特別注意:優化
PLCrashReporter
雖然能提供較爲準確的堆棧信息,用於定位問題,特別是使用符號化策略PLCrashReporterSymbolicationStrategyAll
時,可以對堆棧信息進行符號化,但會消耗大量資源,須要佔用較多時間,致使卡死現象(自測時,耗時超過7s,層屢次到10s以上)。不使用符號化策略
PLCrashReporterSymbolicationStrategyNone
,測試時,平均耗時也接近3s。spa所以,加入該信息採集,須要特別注意,建議僅在開發調試階段使用。線程
爲了投入線上使用,還須要再想一想如何解決該問題。
檢測到卡頓,獲取到調用堆棧信息,客戶端再根據實際狀況進行必定程度的過濾處理,將有價值的信息上報服務器。
後續對服務器收集到的數據進行分析,定位須要優化的功能邏輯。