检测iOS的APP性能的一些方法
首先如果遇到應(yīng)用卡頓或者因?yàn)閮?nèi)存占用過(guò)多時(shí)一般使用Instruments里的來(lái)進(jìn)行檢測(cè)。但對(duì)于復(fù)雜情況可能就需要用到子線程監(jiān)控主線程的方式來(lái)了,下面我對(duì)這些方法做些介紹:
Time Profiler
可以查看多個(gè)線程里那些方法費(fèi)時(shí)過(guò)多的方法。先將右側(cè)Hide System Libraries打上勾,這樣能夠過(guò)濾信息。然后在Call Tree上會(huì)默認(rèn)按照費(fèi)時(shí)的線程進(jìn)行排序,單個(gè)線程中會(huì)也會(huì)按照對(duì)應(yīng)的費(fèi)時(shí)方法排序,選擇方法后能夠通過(guò)右側(cè)Heaviest Stack Trace里雙擊查看到具體的費(fèi)時(shí)操作代碼,從而能夠有針對(duì)性的優(yōu)化,而不需要在一些本來(lái)就不會(huì)怎么影響性能的地方過(guò)度優(yōu)化。
Allocations
這里可以對(duì)每個(gè)動(dòng)作的前后進(jìn)行Generations,對(duì)比內(nèi)存的增加,查看使內(nèi)存增加的具體的方法和代碼所在位置。具體操作是在右側(cè)Generation Analysis里點(diǎn)擊Mark Generation,這樣會(huì)產(chǎn)生一個(gè)Generation,切換到其他頁(yè)面或一段時(shí)間產(chǎn)生了另外一個(gè)事件時(shí)再點(diǎn)Mark Generation來(lái)產(chǎn)生一個(gè)新的Generation,這樣反復(fù),生成多個(gè)Generation,查看這幾個(gè)Generation會(huì)看到Growth的大小,如果太大可以點(diǎn)進(jìn)去查看相應(yīng)占用較大的線程里右側(cè)Heaviest Stack Trace里查看對(duì)應(yīng)的代碼塊,然后進(jìn)行相應(yīng)的處理。
Leak
可以在上面區(qū)域的Leaks部分看到對(duì)應(yīng)的時(shí)間點(diǎn)產(chǎn)生的溢出,選擇后在下面區(qū)域的Statistics>Allocation Summary能夠看到泄漏的對(duì)象,同樣可以通過(guò)Stack Trace查看到具體對(duì)應(yīng)的代碼區(qū)域。
開發(fā)時(shí)需要注意如何避免一些性能問(wèn)題
NSDateFormatter
通過(guò)Instruments的檢測(cè)會(huì)發(fā)現(xiàn)創(chuàng)建NSDateFormatter或者設(shè)置NSDateFormatter的屬性的耗時(shí)總是排在前面,如何處理這個(gè)問(wèn)題呢,比較推薦的是添加屬性或者創(chuàng)建靜態(tài)變量,這樣能夠使得創(chuàng)建初始化這個(gè)次數(shù)降到最低。還有就是可以直接用C,或者這個(gè)NSData的Category來(lái)解決https://github.com/samsoffes/sstoolkit/blob/master/SSToolkit/NSData%2BSSToolkitAdditions.m
UIImage
這里要主要是會(huì)影響內(nèi)存的開銷,需要權(quán)衡下imagedNamed和imageWithContentsOfFile,了解兩者特性后,在只需要顯示一次的圖片用后者,這樣會(huì)減少內(nèi)存的消耗,但是頁(yè)面顯示會(huì)增加Image IO的消耗,這個(gè)需要注意下。由于imageWithContentsOfFile不緩存,所以需要在每次頁(yè)面顯示前加載一次,這個(gè)IO的操作也是需要考慮權(quán)衡的一個(gè)點(diǎn)。
頁(yè)面加載
如果一個(gè)頁(yè)面內(nèi)容過(guò)多,view過(guò)多,這樣將長(zhǎng)頁(yè)面中的需要滾動(dòng)才能看到的那個(gè)部分視圖內(nèi)容通過(guò)開啟新的線程同步的加載。
優(yōu)化首次加載時(shí)間
通過(guò)Time Profier可以查看到啟動(dòng)所占用的時(shí)間,如果太長(zhǎng)可以通過(guò)Heaviest Stack Trace找到費(fèi)時(shí)的方法進(jìn)行改造。
監(jiān)控卡頓的方法
還有種方法是在程序里去監(jiān)控性能問(wèn)題??梢韵瓤纯催@個(gè)Demo,地址https://github.com/ming1016/DecoupleDemo。 這樣在上線后可以通過(guò)這個(gè)程序?qū)⒂脩舻目D操作記錄下來(lái),定時(shí)發(fā)到自己的服務(wù)器上,這樣能夠更大范圍的收集性能問(wèn)題。眾所周知,用戶層面感知的卡頓都是來(lái)自處理所有UI的主線程上,包括在主線程上進(jìn)行的大計(jì)算,大量的IO操作,或者比較重的繪制工作。如何監(jiān)控主線程呢,首先需要知道的是主線程和其它線程一樣都是靠NSRunLoop來(lái)驅(qū)動(dòng)的。可以先看看CFRunLoopRun的大概的邏輯
int32_t __CFRunLoopRun() {__CFRunLoopDoObservers(KCFRunLoopEntry);do{__CFRunLoopDoObservers(kCFRunLoopBeforeTimers); __CFRunLoopDoObservers(kCFRunLoopBeforeSources); //這里開始到kCFRunLoopBeforeWaiting之間處理時(shí)間是感知卡頓的關(guān)鍵地方 __CFRunLoopDoBlocks(); __CFRunLoopDoSource0(); //處理UI事件 //GCD dispatch main queue CheckIfExistMessagesInMainDispatchQueue(); //休眠前 __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting); //等待msg mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts(); //等待中 //休眠后,喚醒 __CFRunLoopDoObservers(kCFRunLoopAfterWaiting); //定時(shí)器喚醒 if (wakeUpPort == timerPort) __CFRunLoopDoTimers(); //異步處理 else if (wakeUpPort == mainDispatchQueuePort) __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() //UI,動(dòng)畫 else __CFRunLoopDoSource1(); //確保同步 __CFRunLoopDoBlocks(); } while (!stop && !timeout); //退出RunLoop __CFRunLoopDoObservers(CFRunLoopExit); }根據(jù)這個(gè)RunLoop我們能夠通過(guò)CFRunLoopObserverRef來(lái)度量。用GCD里的dispatch_semaphore_t開啟一個(gè)新線程,設(shè)置一個(gè)極限值和出現(xiàn)次數(shù)的值,然后獲取主線程上在kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting兩個(gè)狀態(tài)之間的超過(guò)了極限值和出現(xiàn)次數(shù)的場(chǎng)景,將堆棧dump下來(lái),最后發(fā)到服務(wù)器做收集,通過(guò)堆棧能夠找到對(duì)應(yīng)出問(wèn)題的那個(gè)方法。
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保證同步 //創(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用來(lái)進(jìn)行監(jiān)控 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; } //兩個(gè)runloop的狀態(tài),BeforeSources和AfterWaiting這兩個(gè)狀態(tài)區(qū)間時(shí)間能夠檢測(cè)到是否卡頓 if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) { //出現(xiàn)三次出結(jié)果 if (++timeoutCount < 3) { continue; } //將堆棧信息上報(bào)服務(wù)器的代碼放到這里 } //end activity }// end semaphore wait timeoutCount = 0; }// end while }); }有時(shí)候造成卡頓是因?yàn)閿?shù)據(jù)異常,過(guò)多,或者過(guò)大造成的,亦或者是操作的異常出現(xiàn)的,這樣的情況可能在平時(shí)日常開發(fā)測(cè)試中難以遇到,但是在真實(shí)的特別是用戶受眾廣的情況下會(huì)有人出現(xiàn),這樣這種收集卡頓的方式還是有價(jià)值的。
堆棧dump的方法
第一種是直接調(diào)用系統(tǒng)函數(shù)獲取棧信息,這種方法只能夠獲得簡(jiǎn)單的信息,沒法配合dSYM獲得具體哪行代碼出了問(wèn)題,類型也有限。這種方法的主要思路是signal進(jìn)行錯(cuò)誤信號(hào)的獲取。代碼如下
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]; //得到當(dāng)前調(diào)用棧信息 NSString *exceptionReason = [exception reason]; //非常重要,就是崩潰的原因 NSString *exceptionName = [exception name]; //異常類型 } void SignalHandler(int code) { NSLog(@"signal handler = %d",code); } void InitCrashReport() { //系統(tǒng)錯(cuò)誤信號(hào)捕獲 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的話出的報(bào)告看起來(lái)能夠定位到問(wèn)題代碼的具體位置了。
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]; //將字符串上傳服務(wù)器 NSLog(@"lag happen, detail below: %@",lagReportString);下面是測(cè)試Demo里堆棧里的內(nèi)容
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] 費(fèi)時(shí)測(cè)試 2016-03-28 14:59:29.262 HomePageTest[4803:201212] 費(fèi)時(shí)測(cè)試 2016-03-28 14:59:30.865 HomePageTest[4803:201212] 費(fèi)時(shí)測(cè)試 2016-03-28 14:59:32.115 HomePageTest[4803:201212] 費(fèi)時(shí)測(cè)試 2016-03-28 14:59:33.369 HomePageTest[4803:201212] 費(fèi)時(shí)測(cè)試 2016-03-28 14:59:34.832 HomePageTest[4803:201212] 費(fèi)時(shí)測(cè)試 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: 104Exception Type: SIGTRAP Exception Codes: TRAP_TRACE at 0x10aee6f79 Crashed Thread: 2Thread 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 + 1Thread 1: 0 libsystem_kernel.dylib 0x000000010ec6bfde kevent64 + 10 1 libdispatch.dylib 0x000000010e8e6262 _dispatch_mgr_init + 0Thread 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 + 13Thread 3: 0 libsystem_kernel.dylib 0x000000010ec6b6de __workq_kernreturn + 10 1 libsystem_pthread.dylib 0x000000010ec2d365 start_wqthread + 13Thread 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 + 13Thread 5: 0 libsystem_kernel.dylib 0x000000010ec6b6de __workq_kernreturn + 10 1 libsystem_pthread.dylib 0x000000010ec2d365 start_wqthread + 13Thread 6: 0 libsystem_kernel.dylib 0x000000010ec6b6de __workq_kernreturn + 10 1 libsystem_pthread.dylib 0x000000010ec2d365 start_wqthread + 13Thread 7: 0 libsystem_kernel.dylib 0x000000010ec6b6de __workq_kernreturn + 10 1 libsystem_pthread.dylib 0x000000010ec2d365 start_wqthread + 13Thread 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?
?
轉(zhuǎn)載于:https://www.cnblogs.com/fengmin/p/5390040.html
總結(jié)
以上是生活随笔為你收集整理的检测iOS的APP性能的一些方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何 打包整合linux系统文件夹 用于
- 下一篇: 软件工程个人作业05