檢測iOS的APP性能的一些方法

 

首先若是遇到應用卡頓或者由於內存佔用過多時通常使用Instruments裏的來進行檢測。但對於複雜狀況可能就須要用到子線程監控主線程的方式來了,下面我對這些方法作些介紹:

Time Profiler

能夠查看多個線程裏那些方法費時過多的方法。先將右側Hide System Libraries打上勾,這樣可以過濾信息。而後在Call Tree上會默認按照費時的線程進行排序,單個線程中會也會按照對應的費時方法排序,選擇方法後可以經過右側Heaviest Stack Trace裏雙擊查看到具體的費時操做代碼,從而可以有針對性的優化,而不須要在一些原本就不會怎麼影響性能的地方過分優化。git

Allocations

這裏能夠對每一個動做的先後進行Generations,對比內存的增長,查看使內存增長的具體的方法和代碼所在位置。具體操做是在右側Generation Analysis裏點擊Mark Generation,這樣會產生一個Generation,切換到其餘頁面或一段時間產生了另一個事件時再點Mark Generation來產生一個新的Generation,這樣反覆,生成多個Generation,查看這幾個Generation會看到Growth的大小,若是太大能夠點進去查看相應占用較大的線程裏右側Heaviest Stack Trace裏查看對應的代碼塊,而後進行相應的處理。github

Leak

能夠在上面區域的Leaks部分看到對應的時間點產生的溢出,選擇後在下面區域的Statistics>Allocation Summary可以看到泄漏的對象,一樣能夠經過Stack Trace查看到具體對應的代碼區域。緩存

開發時須要注意如何避免一些性能問題

NSDateFormatter

經過Instruments的檢測會發現建立NSDateFormatter或者設置NSDateFormatter的屬性的耗時老是排在前面,如何處理這個問題呢,比較推薦的是添加屬性或者建立靜態變量,這樣可以使得建立初始化這個次數降到最低。還有就是能夠直接用C,或者這個NSData的Category來解決https://github.com/samsoffes/sstoolkit/blob/master/SSToolkit/NSData%2BSSToolkitAdditions.m服務器

UIImage

這裏要主要是會影響內存的開銷,須要權衡下imagedNamed和imageWithContentsOfFile,瞭解二者特性後,在只須要顯示一次的圖片用後者,這樣會減小內存的消耗,可是頁面顯示會增長Image IO的消耗,這個須要注意下。因爲imageWithContentsOfFile不緩存,因此須要在每次頁面顯示前加載一次,這個IO的操做也是須要考慮權衡的一個點。markdown

頁面加載

若是一個頁面內容過多,view過多,這樣將長頁面中的須要滾動才能看到的那個部分視圖內容經過開啓新的線程同步的加載。app

優化首次加載時間

經過Time Profier能夠查看到啓動所佔用的時間,若是太長能夠經過Heaviest Stack Trace找到費時的方法進行改造。異步

監控卡頓的方法

還有種方法是在程序裏去監控性能問題。能夠先看看這個Demo,地址https://github.com/ming1016/DecoupleDemo。 這樣在上線後能夠經過這個程序將用戶的卡頓操做記錄下來,定時發到本身的服務器上,這樣可以更大範圍的收集性能問題。衆所周知,用戶層面感知的卡頓都是來自處理全部UI的主線程上,包括在主線程上進行的大計算,大量的IO操做,或者比較重的繪製工做。如何監控主線程呢,首先須要知道的是主線程和其它線程同樣都是靠NSRunLoop來驅動的。能夠先看看CFRunLoopRun的大概的邏輯async

int32_t __CFRunLoopRun() { __CFRunLoopDoObservers(KCFRunLoopEntry); do { __CFRunLoopDoObservers(kCFRunLoopBeforeTimers); __CFRunLoopDoObservers(kCFRunLoopBeforeSources); //這裏開始到kCFRunLoopBeforeWaiting之間處理時間是感知卡頓的關鍵地方 __CFRunLoopDoBlocks(); __CFRunLoopDoSource0(); //處理UI事件 //GCD dispatch main queue CheckIfExistMessagesInMainDispatchQueue(); //休眠前 __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting); //等待msg mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts(); //等待中 //休眠後,喚醒 __CFRunLoopDoObservers(kCFRunLoopAfterWaiting); //定時器喚醒 if (wakeUpPort == timerPort) __CFRunLoopDoTimers(); //異步處理 else if (wakeUpPort == mainDispatchQueuePort) __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() //UI,動畫 else __CFRunLoopDoSource1(); //確保同步 __CFRunLoopDoBlocks(); } while (!stop && !timeout); //退出RunLoop __CFRunLoopDoObservers(CFRunLoopExit); }

根據這個RunLoop咱們可以經過CFRunLoopObserverRef來度量。用GCD裏的dispatch_semaphore_t開啓一個新線程,設置一個極限值和出現次數的值,而後獲取主線程上在kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting兩個狀態之間的超過了極限值和出現次數的場景,將堆棧dump下來,最後發到服務器作收集,經過堆棧可以找到對應出問題的那個方法。ide

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { MyClass *object = (__bridge MyClass*)info; object->activity = activity; } 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); } - (void)endMonitor { if (!runLoopObserver) { return; } CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes); CFRelease(runLoopObserver); runLoopObserver = NULL; } - (void)beginMonitor { if (runLoopObserver) { return; } dispatchSemaphore = dispatch_semaphore_create(0); //Dispatch Semaphore保證同步 //建立一個觀察者 CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context); //將觀察者添加到主線程runloop的common模式下的觀察中 CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes); //建立子線程監控 dispatch_async(dispatch_get_global_queue(0, 0), ^{ //子線程開啓一個持續的loop用來進行監控 while (YES) { long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 30*NSEC_PER_MSEC)); if (semaphoreWait != 0) { if (!runLoopObserver) { timeoutCount = 0; dispatchSemaphore = 0; runLoopActivity = 0; return; } //兩個runloop的狀態,BeforeSources和AfterWaiting這兩個狀態區間時間可以檢測到是否卡頓 if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) { //出現三次出結果 if (++timeoutCount < 3) { continue; } //將堆棧信息上報服務器的代碼放到這裏 } //end activity }// end semaphore wait timeoutCount = 0; }// end while }); }

有時候形成卡頓是由於數據異常,過多,或者過大形成的,亦或者是操做的異常出現的,這樣的狀況可能在平時平常開發測試中難以遇到,可是在真實的特別是用戶受衆廣的狀況下會有人出現,這樣這種收集卡頓的方式仍是有價值的。函數

堆棧dump的方法

第一種是直接調用系統函數獲取棧信息,這種方法只可以得到簡單的信息,無法配合dSYM得到具體哪行代碼出了問題,類型也有限。這種方法的主要思路是signal進行錯誤信號的獲取。代碼以下

static int s_fatal_signals[] = { SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP, SIGTERM, SIGKILL, }; static int s_fatal_signal_num = sizeof(s_fatal_signals) / sizeof(s_fatal_signals[0]); void UncaughtExceptionHandler(NSException *exception) { NSArray *exceptionArray = [exception callStackSymbols]; //獲得當前調用棧信息 NSString *exceptionReason = [exception reason]; //很是重要,就是崩潰的緣由 NSString *exceptionName = [exception name]; //異常類型 } void SignalHandler(int code) { NSLog(@"signal handler = %d",code); } void InitCrashReport() { //系統錯誤信號捕獲 for (int i = 0; i < s_fatal_signal_num; ++i) { signal(s_fatal_signals[i], SignalHandler); } //oc未捕獲異常的捕獲 NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler); } int main(int argc, char * argv[]) { @autoreleasepool { InitCrashReport(); return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }

使用PLCrashReporter的話出的報告看起來可以定位到問題代碼的具體位置了。

NSData *lagData = [[[PLCrashReporter alloc] initWithConfiguration:[[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]] generateLiveReport]; PLCrashReport *lagReport = [[PLCrashReport alloc] initWithData:lagData error:NULL]; NSString *lagReportString = [PLCrashReportTextFormatter stringValueForCrashReport:lagReport withTextFormat:PLCrashReportTextFormatiOS]; //將字符串上傳服務器 NSLog(@"lag happen, detail below: %@",lagReportString);

下面是測試Demo裏堆棧裏的內容

2016-03-28 14:59:26.922 HomePageTest[4803:201212]  INFO: Reveal Server started (Protocol Version 25).
2016-03-28 14:59:27.134 HomePageTest[4803:201212] 費時測試
2016-03-28 14:59:29.262 HomePageTest[4803:201212] 費時測試
2016-03-28 14:59:30.865 HomePageTest[4803:201212] 費時測試
2016-03-28 14:59:32.115 HomePageTest[4803:201212] 費時測試
2016-03-28 14:59:33.369 HomePageTest[4803:201212] 費時測試
2016-03-28 14:59:34.832 HomePageTest[4803:201212] 費時測試
2016-03-28 14:59:34.836 HomePageTest[4803:201615] lag happen, detail below: 
 Incident Identifier: 73BEF9D2-EBE3-49DF-B95B-7392635631A3
CrashReporter Key:   TODO
Hardware Model:      x86_64
Process:         HomePageTest [4803]
Path:            /Users/daiming/Library/Developer/CoreSimulator/Devices/444AAB95-C393-45CC-B5DC-0FB8611068F9/data/Containers/Bundle/Application/9CEE3A3A-9469-44F5-8112-FF0550ED8009/HomePageTest.app/HomePageTest
Identifier:      com.xiaojukeji.HomePageTest
Version:         1.0 (1)
Code Type:       X86-64
Parent Process:  debugserver [4805]

Date/Time:       2016-03-28 06:59:34 +0000
OS Version:      Mac OS X 9.2 (15D21)
Report Version:  104

Exception Type:  SIGTRAP
Exception Codes: TRAP_TRACE at 0x10aee6f79
Crashed Thread:  2

Thread 0:
0   libsystem_kernel.dylib              0x000000010ec6b206 __semwait_signal + 10
1   libsystem_c.dylib                   0x000000010e9f2b9e usleep + 54
2   HomePageTest                        0x000000010aedf934 -[TestTableView configCell] + 820
3   HomePageTest                        0x000000010aee5c89 -[testTableViewController observeValueForKeyPath:ofObject:change:context:] + 601
4   Foundation                          0x000000010b9c2564 NSKeyValueNotifyObserver + 347
5   Foundation                          0x000000010b9c178f NSKeyValueDidChange + 466
6   Foundation                          0x000000010b9bf003 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 1176
7   Foundation                          0x000000010ba1d35f _NSSetObjectValueAndNotify + 261
8   HomePageTest                        0x000000010aec9c26 -[DCTableView tableView:cellForRowAtIndexPath:] + 262
9   UIKit                               0x000000010c872e43 -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 766
10  UIKit                               0x000000010c872f7b -[UITableView _createPreparedCellForGlobalRow:willDisplay:] + 74
11  UIKit                               0x000000010c847a39 -[UITableView _updateVisibleCellsNow:isRecursive:] + 2996
12  UIKit                               0x000000010c87c01c -[UITableView _performWithCachedTraitCollection:] + 92
13  UIKit                               0x000000010c862edc -[UITableView layoutSubviews] + 224
14  UIKit                               0x000000010c7d04a3 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 703
15  QuartzCore                          0x000000010c49a59a -[CALayer layoutSublayers] + 146
16  QuartzCore                          0x000000010c48ee70 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 366
17  QuartzCore                          0x000000010c48ecee _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
18  QuartzCore                          0x000000010c483475 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 277
19  QuartzCore                          0x000000010c4b0c0a _ZN2CA11Transaction6commitEv + 486
20  QuartzCore                          0x000000010c4bf9f4 _ZN2CA7Display11DisplayLink14dispatch_itemsEyyy + 576
21  CoreFoundation                      0x000000010e123c84 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
22  CoreFoundation                      0x000000010e123831 __CFRunLoopDoTimer + 1089
23  CoreFoundation                      0x000000010e0e5241 __CFRunLoopRun + 1937
24  CoreFoundation                      0x000000010e0e4828 CFRunLoopRunSpecific + 488
25  GraphicsServices                    0x0000000110479ad2 GSEventRunModal + 161
26  UIKit                               0x000000010c719610 UIApplicationMain + 171
27  HomePageTest                        0x000000010aee0fdf main + 111
28  libdyld.dylib                       0x000000010e92b92d start + 1

Thread 1:
0   libsystem_kernel.dylib              0x000000010ec6bfde kevent64 + 10
1   libdispatch.dylib                   0x000000010e8e6262 _dispatch_mgr_init + 0

Thread 2 Crashed:
0   HomePageTest                        0x000000010b04a445 -[PLCrashReporter generateLiveReportWithThread:error:] + 632
1   HomePageTest                        0x000000010aee6f79 __28-[SMLagMonitor beginMonitor]_block_invoke + 425
2   libdispatch.dylib                   0x000000010e8d6e5d _dispatch_call_block_and_release + 12
3   libdispatch.dylib                   0x000000010e8f749b _dispatch_client_callout + 8
4   libdispatch.dylib                   0x000000010e8dfbef _dispatch_root_queue_drain + 1829
5   libdispatch.dylib                   0x000000010e8df4c5 _dispatch_worker_thread3 + 111
6   libsystem_pthread.dylib             0x000000010ec2f68f _pthread_wqthread + 1129
7   libsystem_pthread.dylib             0x000000010ec2d365 start_wqthread + 13

Thread 3:
0   libsystem_kernel.dylib              0x000000010ec6b6de __workq_kernreturn + 10
1   libsystem_pthread.dylib             0x000000010ec2d365 start_wqthread + 13

Thread 4:
0   libsystem_kernel.dylib              0x000000010ec65386 mach_msg_trap + 10
1   CoreFoundation                      0x000000010e0e5b64 __CFRunLoopServiceMachPort + 212
2   CoreFoundation                      0x000000010e0e4fbf __CFRunLoopRun + 1295
3   CoreFoundation                      0x000000010e0e4828 CFRunLoopRunSpecific + 488
4   WebCore                             0x0000000113408f65 _ZL12RunWebThreadPv + 469
5   libsystem_pthread.dylib             0x000000010ec2fc13 _pthread_body + 131
6   libsystem_pthread.dylib             0x000000010ec2fb90 _pthread_body + 0
7   libsystem_pthread.dylib             0x000000010ec2d375 thread_start + 13

Thread 5:
0   libsystem_kernel.dylib              0x000000010ec6b6de __workq_kernreturn + 10
1   libsystem_pthread.dylib             0x000000010ec2d365 start_wqthread + 13

Thread 6:
0   libsystem_kernel.dylib              0x000000010ec6b6de __workq_kernreturn + 10
1   libsystem_pthread.dylib             0x000000010ec2d365 start_wqthread + 13

Thread 7:
0   libsystem_kernel.dylib              0x000000010ec6b6de __workq_kernreturn + 10
1   libsystem_pthread.dylib             0x000000010ec2d365 start_wqthread + 13

Thread 2 crashed with X86-64 Thread State:
   rip: 0x000000010b04a445    rbp: 0x0000700000093da0    rsp: 0x0000700000093b10    rax: 0x0000700000093b70 
   rbx: 0x0000700000093cb0    rcx: 0x0000000000001003    rdx: 0x0000000000000000    rdi: 0x000000010b04a5ca 
   rsi: 0x0000700000093b40     r8: 0x0000000000000014     r9: 0x0000000000000000    r10: 0x000000010ec65362 
   r11: 0x0000000000000246    r12: 0x00007fdaf5800940    r13: 0x0000000000000000    r14: 0x0000000000000009 
   r15: 0x0000700000093b90 rflags: 0x0000000000000202     cs: 0x000000000000002b     fs: 0x0000000000000000 
    gs: 0x0000000000000000
相關文章
相關標籤/搜索