能够查看多个线程里那些方法费时过多的方法。先将右侧Hide System Libraries打上勾,这样可以过滤信息。而后在Call Tree上会默认按照费时的线程进行排序,单个线程中会也会按照对应的费时方法排序,选择方法后可以经过右侧Heaviest Stack Trace里双击查看到具体的费时操做代码,从而可以有针对性的优化,而不须要在一些原本就不会怎么影响性能的地方过分优化。git
这里能够对每一个动做的先后进行Generations,对比内存的增长,查看使内存增长的具体的方法和代码所在位置。具体操做是在右侧Generation Analysis里点击Mark Generation,这样会产生一个Generation,切换到其余页面或一段时间产生了另一个事件时再点Mark Generation来产生一个新的Generation,这样反复,生成多个Generation,查看这几个Generation会看到Growth的大小,若是太大能够点进去查看相应占用较大的线程里右侧Heaviest Stack Trace里查看对应的代码块,而后进行相应的处理。github
能够在上面区域的Leaks部分看到对应的时间点产生的溢出,选择后在下面区域的Statistics>Allocation Summary可以看到泄漏的对象,一样能够经过Stack Trace查看到具体对应的代码区域。缓存
经过Instruments的检测会发现建立NSDateFormatter或者设置NSDateFormatter的属性的耗时老是排在前面,如何处理这个问题呢,比较推荐的是添加属性或者建立静态变量,这样可以使得建立初始化这个次数降到最低。还有就是能够直接用C,或者这个NSData的Category来解决https://github.com/samsoffes/sstoolkit/blob/master/SSToolkit/NSData%2BSSToolkitAdditions.m服务器
这里要主要是会影响内存的开销,须要权衡下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 }); }
有时候形成卡顿是由于数据异常,过多,或者过大形成的,亦或者是操做的异常出现的,这样的状况可能在平时平常开发测试中难以遇到,可是在真实的特别是用户受众广的状况下会有人出现,这样这种收集卡顿的方式仍是有价值的。函数
第一种是直接调用系统函数获取栈信息,这种方法只可以得到简单的信息,无法配合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