首先,如果遇到应用卡顿或者内存占用过大,一般会使用Instruments进行测试。但是对于复杂的情况,可能需要使用子线程来监听主线程。下面我将介绍这些方法:TimeProfiler可以检查多个线程中耗时过多的方法。首先勾选右边的HideSystemLibraries,这样可以过滤信息。然后在CallTree上,会默认对耗时线程进行排序,单个线程也会按照对应的耗时方法进行排序。选择方法后,可以在右侧的HeaviestStackTrace中双击查看具体耗时的操作代码,这样可以有针对性的优化,不会在一些不影响性能的地方过度优化。Allocations这里可以在每个动作前后进行Generations,比较内存的增加,查看增加内存的具体方法和代码位置。具体操作是在右侧的GenerationAnalysis中点击MarkGeneration,会生成一个Generation,然后在切换到其他页面或者其他事件发生一段时间后,再点击MarkGeneration生成一个新的Generation,重复此操作生成多个Generations的方式,勾选这些Generations就会看到Growth的大小。如果太大,可以在占用量大的线程右侧的HeaviestStackTrace中点击查看相应的代码块,然后进行相应的处理。Leak可以在上方区域的Leaks部分看到相应时间点产生的溢出。选中后,在下方的Statistics>AllocationSummary中可以看到泄露的对象。也可以通过StackTrace查看具体对应的代码区。在开发的时候需要注意如何避免一些性能问题。NSDateFormatter通过Instruments的检测会发现创建NSDateFormatter或者设置NSDateFormatter的属性的耗时总是排在前面的。如何处理这个问题?建议添加属性或创建静态变量。它可以将创建和初始化的次数减少到最少。还有,可以直接用C,或者这个NSData类解决https://github.com/samsoffes/sstoolkit/blob/master/SSToolkit/NSData%2BSSToolkitAdditions.mUIImage这里主要影响内存开销,需要权衡下imagedNamed和imageWithContentsOfFile,了解了两者的特点后,对于只需要显示一次的图片使用后者,会减少内存消耗,但是页面显示会增加ImageIO消耗,需要注意。由于imageWithContentsOfFile没有被缓存,所以需要在每页显示前加载一次。这个IO操作也是需要权衡考虑的一个点。页面加载如果一个页面内容太多,view太多,那么在长页面中需要滚动才能看到的那部分view的内容,会通过开启一个新的线程来同步加载。优化最新加载时间。您可以通过TimeProfiler查看启动所用的时间。如果太长,可以通过HeaviestStackTrace找一个耗时的方法修改。监控滞后的另一种方法是监控程序中的性能问题。大家可以先看看这个Demo,地址https://github.com/ming1016/DecoupleDemo。这样上线后,就可以使用本程序记录用户的卡顿操作,定时发送到自己的服务器上,这样就可以在更大范围内收集性能问题。众所周知,用户层面感知到的滞后来自于处理所有UI的主线程,包括大的计算,大量的IO操作,或者比较重的绘图任务都在主线程上执行。如何监控主线程,首先要知道主线程和其他线程一样,都是由NSRunLoop驱动的。可以先看一下CFRunLoopRunint32_t__CFRunLoopRun(){__CFRunLoopDoObservers(KCFRunLoopEntry);do{__CFRunLoopDoObservers(kCFRunLoopBeforeTimers);__CFRunLoopDoObservers(kCFRunLoopBeforeSources);//从这里到kCFRunLoopBeforeWaitings的处理时间是感知锁的关键);__CFRunLoopDoSource0();//处理UI事件//GCDdispatchmainqueueCheckIfExistMessagesInMainDispatchQueue();//睡眠前);//定时器唤醒if(wakeUpPort==timerPort)__CFRunLoopDoTimers();//异步处理elseif(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这两个状态之间超过限制值和出现次数的场景主线程,以及stackdumpdown后,将***发送到server进行收集,通过stack可以找到问题对应的方法。staticvoidrunLoopObserverCallBack(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity,void*info){MyClass*object=(__bridgeMyClass*)info;object->activity=activity;}staticvoidrunLoopObserverCallBack(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity,void*info){SMLagMonitor*lagMonitor=(__bridgeSMLagMonitor*);}lagMonitor->runLoopActivity=activity;dispatch_semaphore_tsemaphore=lagMonitor->dispatchSemaphore;dispatch_semaphore_signal(semaphore);}-(void)endMonitor{if(!runLoopObserver){return;}CFRunLoopRemoveObserver(CFRunLoopGetMain(),runLoopObserver,kCFRunLoopCommonbaseModes(run);LoopCFRelebaseModes);;runLoopObserver=NULL;}-(void)beginMonitor{if(runLoopObserver){return;}dispatchSemaphore=dispatch_semaphore_create(0);//DispatchSemaphore保证同步//创建一个观察者CFRunLoopObserverContextcontext={0,(__bridgevoid*)self,NULL,NULL};runLoopObserver=CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0,&runLoopObserverCallBack,&context);//添加观察者到主线程runloop普通模式的观察CFRunLoopAddObserver(CFRunLoopGetMain(),runLoopObserver,kCFRunLoopCommonModes);//创建子线程监听dispatch_async(dispatch_get_global_queue(0,0),^{//子线程开始连续循环监听while(YES){longsemaphoreWait=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){//有3个结果if(++timeoutCount3){continue;}//这里把栈信息放到服务端代码}//endactivity}//endsemaphorewaittimeoutCount=0;}//endwhile});}有时会造成卡顿是异常数据引起的,太多,或者太多大,或异常操作。这样的情况在日常的开发测试中可能很难遇到,但是在真实的情况下是会发生的,尤其是在用户受众广泛的情况下就会有人出现,所以这种收集卡顿的方法仍然是一个有价值的stackdump方法。第一种是直接调用系统函数获取堆栈信息。该方法只能获取简单信息,不能配合dSYM获取具体线路。代码有问题,类型有限。这种方法的主要思想是通过信号获取误差信号。代码如下];//获取当前的调用栈信息NSString*exceptionReason=[exceptionreason];//很重要,是崩溃的原因NSString*exceptionName=[exceptionname];//异常类型}voidSignalHandler(intcode){NSLog(@"signalhandler=%d",code);}voidInitCrashReport(){//系统错误信号捕获for(inti=0;isignal(s_fatal_signals[i],SignalHandler);}//oc未捕获异常捕获NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);}intmain(intargc,char*argv[]){@autoreleasepool{InitCrashReport();returnUIApplicationMain(argc,argv,nil,NSStringFromClass([AppDelegateclass]));}}使用PLCrashReporter,报告似乎可以定位问题代码的具体位置。NSData*lagData=[[[PLCrashReporteralloc]initWithConfiguration:[[PLCrashReporterConfigalloc]initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSDsymbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]]generateLiveReport];PLCrashReport*lagReport=[[PLCrashReportalloc]initWithData:lagDataerror:NULL];NSString*lagReportString=[PLCrashReportTextFormatterstringValueForCrashReport:lagReportwithTextFormat:PLCrashReportTextFormatiOS];//上传字符串到服务器NSLog(@"laghappen,detailbelow:%@",lagReportString);测试Demo中栈内容超出微信字数,本文省略
