检测iOS的APP 性能的一些方法

首先如果遇到应用卡顿或者因为内存占用过多时一般使用Instruments里的来进行检测。但对于复杂情况可能就需要用到子线程监控主线程的方式来了,下面我对这些方法做些介绍:

Time Profiler

可以查看多个线程里那些方法费时过多的方法。先将右侧Hide System Libraries打上勾,这样能够过滤信息。然后在Call Tree上会默认按照费时的线程进行排序,单个线程中会也会按照对应的费时方法排序,选择方法后能够通过右侧Heaviest Stack Trace里双击查看到具体的费时操作代码,从而能够有针对性的优化,而不需要在一些本来就不会怎么影响性能的地方过度优化。

Allocations

这里可以对每个动作的前后进行Generations,对比内存的增加,查看使内存增加的具体的方法和代码所在位置。具体操作是在右侧Generation Analysis里点击Mark Generation,这样会产生一个Generation,切换到其他页面或一段时间产生了另外一个事件时再点Mark Generation来产生一个新的Generation,这样反复,生成多个Generation,查看这几个Generation会看到Growth的大小,如果太大可以点进去查看相应占用较大的线程里右侧Heaviest Stack Trace里查看对应的代码块,然后进行相应的处理。

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的操作也是需要考虑权衡的一个点。

页面加载

如果一个页面内容过多,view过多,这样将长页面中的需要滚动才能看到的那个部分视图内容通过开启新的线程同步的加载。

优化首次加载时间

通过Time Profier可以查看到启动所占用的时间,如果太长可以通过Heaviest Stack Trace找到费时的方法进行改造。

监控卡顿的方法

还有种方法是在程序里去监控性能问题。可以先看看这个Demo,地址https://github.com/ming1016/DecoupleDemo。 这样在上线后可以通过这个程序将用户的卡顿操作记录下来,定时发到自己的服务器上,这样能够更大范围的收集性能问题。众所周知,用户层面感知的卡顿都是来自处理所有UI的主线程上,包括在主线程上进行的大计算,大量的IO操作,或者比较重的绘制工作。如何监控主线程呢,首先需要知道的是主线程和其它线程一样都是靠NSRunLoop来驱动的。可以先看看CFRunLoopRun的大概的逻辑


  1. int32_t __CFRunLoopRun() 
  2.  
  3.  
  4.     __CFRunLoopDoObservers(KCFRunLoopEntry); 
  5.  
  6.     do 
  7.  
  8.     { 
  9.  
  10.         __CFRunLoopDoObservers(kCFRunLoopBeforeTimers); 
  11.  
  12.         __CFRunLoopDoObservers(kCFRunLoopBeforeSources); //这里开始到kCFRunLoopBeforeWaiting之间处理时间是感知卡顿的关键地方 
  13.  
  14.   
  15.  
  16.         __CFRunLoopDoBlocks(); 
  17.  
  18.         __CFRunLoopDoSource0(); //处理UI事件 
  19.  
  20.   
  21.  
  22.         //GCD dispatch main queue 
  23.  
  24.         CheckIfExistMessagesInMainDispatchQueue(); 
  25.  
  26.   
  27.  
  28.         //休眠前 
  29.  
  30.         __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting); 
  31.  
  32.   
  33.  
  34.         //等待msg 
  35.  
  36.         mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts(); 
  37.  
  38.   
  39.  
  40.         //等待中 
  41.  
  42.   
  43.  
  44.         //休眠后,唤醒 
  45.  
  46.         __CFRunLoopDoObservers(kCFRunLoopAfterWaiting); 
  47.  
  48.   
  49.  
  50.         //定时器唤醒 
  51.  
  52.         if (wakeUpPort == timerPort) 
  53.  
  54.             __CFRunLoopDoTimers(); 
  55.  
  56.   
  57.  
  58.         //异步处理 
  59.  
  60.         else if (wakeUpPort == mainDispatchQueuePort) 
  61.  
  62.             __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() 
  63.  
  64.   
  65.  
  66.         //UI,动画 
  67.  
  68.         else 
  69.  
  70.             __CFRunLoopDoSource1(); 
  71.  
  72.   
  73.  
  74.         //确保同步 
  75.  
  76.         __CFRunLoopDoBlocks(); 
  77.  
  78.   
  79.  
  80.     } while (!stop && !timeout); 
  81.  
  82.   
  83.  
  84.     //退出RunLoop 
  85.  
  86.     __CFRunLoopDoObservers(CFRunLoopExit); 
  87.  

根据这个RunLoop我们能够通过CFRunLoopObserverRef来度量。用GCD里的dispatch_semaphore_t开启一个新线程,设置一个极限值和出现次数的值,然后获取主线程上在kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting两个状态之间的超过了极限值和出现次数的场景,将堆栈dump下来,最后发到服务器做收集,通过堆栈能够找到对应出问题的那个方法。


  1. static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) 
  2.  
  3.  
  4.     MyClass *object = (__bridge MyClass*)info; 
  5.  
  6.     object->activity = activity; 
  7.  
  8.  
  9.   
  10.  
  11. static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){ 
  12.  
  13.     SMLagMonitor *lagMonitor = (__bridge SMLagMonitor*)info; 
  14.  
  15.     lagMonitor->runLoopActivity = activity; 
  16.  
  17.   
  18.  
  19.     dispatch_semaphore_t semaphore = lagMonitor->dispatchSemaphore; 
  20.  
  21.     dispatch_semaphore_signal(semaphore); 
  22.  
  23.  
  24.   
  25.  
  26. - (void)endMonitor { 
  27.  
  28.     if (!runLoopObserver) { 
  29.  
  30.         return; 
  31.  
  32.     } 
  33.  
  34.     CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes); 
  35.  
  36.     CFRelease(runLoopObserver); 
  37.  
  38.     runLoopObserver = NULL; 
  39.  
  40.  
  41.   
  42.  
  43. - (void)beginMonitor { 
  44.  
  45.     if (runLoopObserver) { 
  46.  
  47.         return; 
  48.  
  49.     } 
  50.  
  51.     dispatchSemaphore = dispatch_semaphore_create(0); //Dispatch Semaphore保证同步 
  52.  
  53.     //创建一个观察者 
  54.  
  55.     CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; 
  56.  
  57.     runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, 
  58.  
  59.                                               kCFRunLoopAllActivities, 
  60.  
  61.                                               YES, 
  62.  
  63.                                               0, 
  64.  
  65.                                               &runLoopObserverCallBack, 
  66.  
  67.                                               &context); 
  68.  
  69.     //将观察者添加到主线程runloop的common模式下的观察中 
  70.  
  71.     CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes); 
  72.  
  73.   
  74.  
  75.     //创建子线程监控 
  76.  
  77.     dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
  78.  
  79.         //子线程开启一个持续的loop用来进行监控 
  80.  
  81.         while (YES) { 
  82.  
  83.             long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 30*NSEC_PER_MSEC)); 
  84.  
  85.             if (semaphoreWait != 0) { 
  86.  
  87.                 if (!runLoopObserver) { 
  88.  
  89.                     timeoutCount = 0; 
  90.  
  91.                     dispatchSemaphore = 0; 
  92.  
  93.                     runLoopActivity = 0; 
  94.  
  95.                     return; 
  96.  
  97.                 } 
  98.  
  99.                 //两个runloop的状态,BeforeSources和AfterWaiting这两个状态区间时间能够检测到是否卡顿 
  100.  
  101.                 if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) { 
  102.  
  103.                     //出现三次出结果 
  104.  
  105.                     if (++timeoutCount 3) { 
  106.  
  107.                         continue; 
  108.  
  109.                     } 
  110.  
  111.   
  112.  
  113.                     //将堆栈信息上报服务器的代码放到这里 
  114.  
  115.   
  116.  
  117.                 } //end activity 
  118.  
  119.             }// end semaphore wait 
  120.  
  121.             timeoutCount = 0; 
  122.  
  123.         }// end while 
  124.  
  125.     }); 
  126.  
  127.   
  128.  

有时候造成卡顿是因为数据异常,过多,或者过大造成的,亦或者是操作的异常出现的,这样的情况可能在平时日常开发测试中难以遇到,但是在真实的特别是用户受众广的情况下会有人出现,这样这种收集卡顿的方式还是有价值的。

堆栈dump的方法

第一种是直接调用系统函数获取栈信息,这种方法只能够获得简单的信息,没法配合dSYM获得具体哪行代码出了问题,类型也有限。这种方法的主要思路是signal进行错误信号的获取。代码如下


  1. static int s_fatal_signals[] = { 
  2.  
  3.     SIGABRT, 
  4.  
  5.     SIGBUS, 
  6.  
  7.     SIGFPE, 
  8.  
  9.     SIGILL, 
  10.  
  11.     SIGSEGV, 
  12.  
  13.     SIGTRAP, 
  14.  
  15.     SIGTERM, 
  16.  
  17.     SIGKILL, 
  18.  
  19. }; 
  20.  
  21.   
  22.  
  23. static int s_fatal_signal_num = sizeof(s_fatal_signals) / sizeof(s_fatal_signals[0]); 
  24.  
  25.   
  26.  
  27. void UncaughtExceptionHandler(NSException *exception) { 
  28.  
  29.     NSArray *exceptionArray = [exception callStackSymbols]; //得到当前调用栈信息 
  30.  
  31.     NSString *exceptionReason = [exception reason];       //非常重要,就是崩溃的原因 
  32.  
  33.     NSString *exceptionName = [exception name];           //异常类型 
  34.  
  35.  
  36.   
  37.  
  38. void SignalHandler(int code) 
  39.  
  40.  
  41.     NSLog(@"signal handler = %d",code); 
  42.  
  43.  
  44.   
  45.  
  46. void InitCrashReport() 
  47.  
  48.  
  49.     //系统错误信号捕获 
  50.  
  51.     for (int i = 0; i signal(s_fatal_signals[i], SignalHandler); 
  52.  
  53.     } 
  54.  
  55.   
  56.  
  57.     //oc未捕获异常的捕获 
  58.  
  59.     NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler); 
  60.  
  61.  
  62. int main(int argc, char * argv[]) { 
  63.  
  64.     @autoreleasepool { 
  65.  
  66.         InitCrashReport(); 
  67.  
  68.         return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 
  69.  
  70.     } 
  71.  

使用PLCrashReporter的话出的报告看起来能够定位到问题代码的具体位置了。


  1. NSData *lagData = [[[PLCrashReporter alloc] 
  2.  
  3.                                           initWithConfiguration:[[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]] generateLiveReport]; 
  4.  
  5. PLCrashReport *lagReport = [[PLCrashReport alloc] initWithData:lagData error:NULL]; 
  6.  
  7. NSString *lagReportString = [PLCrashReportTextFormatter stringValueForCrashReport:lagReport withTextFormat:PLCrashReportTextFormatiOS]; 
  8.  
  9. //将字符串上传服务器 
  10.  
  11. NSLog(@"lag happen, detail below: 
  12.  
  13. %@",lagReportString); 

测试Demo里堆栈中的内容,超过了微信正文字数,所以本文省略了

本文作者:佚名

来源:51CTO

时间: 2024-09-27 13:06:38

检测iOS的APP 性能的一些方法的相关文章

移动App性能评测与优化

实战 移动App性能评测与优化 TMQ专项测试团队 编著  图书在版编目(CIP)数据 移动App性能评测与优化/ TMQ专项测试团队编著. -北京:机械工业出版社,2016.9 (实战) ISBN 978-7-111-54826-3 I. 移- II. T- III. 移动终端-应用程序–程序测试–研究 IV. TN929.53 中国版本图书馆CIP数据核字(2016)第213174号 本书通过六个专题方向介绍腾讯公司移动互联网事业群在移动应用性能评测优化方面的实战经验,涉及内存.电量.流畅度

Android App性能评测分析-cpu占用篇

1.前言 很多时候在使用APP的时候,手机可能会发热发烫.这是因为CPU使用率过高,CPU过于繁忙,会使整个手机无法响应用户,整体性能降低,用户体验就会很差,也容易引起ANR等等一系列问题.以下会根据实际app性能测试案例,展开进行app性能评测之CPU使用率的分析和总结. CPU使用率原理理解 在Linux系统下,CPU利用率分为用户态.系统态.空闲态,分别表示CPU处于用户态执行的时间,系统内核执行的时间,和空闲系统进程执行的时间. 平时所说的CPU利用率是指:CPU执行非系统空闲进程的时间

Android App性能评测分析-网络流量篇

1. 前言 移动互联网发展到现在,虽然用户的联网方式已经完成了3G/4G网络依赖到Wifi依赖的转变,但是过多以及没有经过处理的网络请求,会消耗用户的网络流量,造成用户流量费用(金钱)的损失,高流量的消耗必然导致非WIFI场景用户的流失,流量测试在性能评测中势必会占较大的权重.下面会根据实际app性能测试案例,展开进行app性能评测之网络流量的分析和总结. 2. 流量测试方法 2.1 流量理解 运营商替我们的手机转发数据报文,数据报文的总大小(字节数)即流量,这里的数据报文包含手机上下行的报文.

判断Win7笔记本配置性能的评判方法

  现在大家都喜欢自己组装电脑,或者去购买人家已经组装好的电脑,这样就可以根据自己的要求来配置电脑,还是个不错的选择,因为电脑的配置是直接关系到电脑的运行速度的,如果电脑的配置比较低的话,无疑是会对电脑的运行产生影响,比如说电脑处理器的运行.内存的运行等等,都能直观的反应笔记本的配置情况.下面小编介绍一个方法,可以帮助大家查看电脑的配置情况,怎么判断Win7笔记本配置性能到底好不好,一起跟随小编来看看吧! 判断Win7笔记本配置性能的评判方法 1.我们在电脑的桌面看到有一个计算机的图标,大家把鼠

iOS 的 APP 在系统中如何适应 iPhone 5s/6/6 Plus 三种屏幕的尺寸?

初代iPhone 2007年,初代iPhone发布,屏幕的宽高是 320 x 480 像素.下文也是按照宽度,高度的顺序排列.这个分辨率一直到iPhone 3GS也保持不变. 那时编写iOS的App(应用程序),只支持绝对定位.比如一个按钮(x, y, width, height) = (20, 30, 40, 50),就表示它的宽度是40像素,高度是50像素,放在(20, 30)像素的位置. iPhone 4 2010年,iPhone 4发布,率先采用Retina显示屏,在屏幕的物理尺寸不变的

优化Android App性能?十大技巧必知!

http://blog.csdn.net/qijianke2014/article/details/40041331 无论锤子还是茄子手机的不断冒出,Android系统的手机市场占有率目前来说还是最大的,因此基于Android开发的App数量也是很庞大的.那么,如何能开发出更高性能的Android App?相信是软件开发公司以及广大程序员们头疼的一大难题.今天,就给大家提供几个提高Android App性能的技巧. 高效地利用线程 1.在后台取消一些线程中的动作 我们知道App运行过程中所有的操

Pury — 一个新的 Android App 性能分析工具

本文讲的是Pury - 一个新的 Android App 性能分析工具, 手机应用存在的目的,就是在帮助用户做他们想做的事情的同时,提供最好的用户体验 -- 而用户体验的重中之重是应用的性能.但有时候开发者们却以性能为借口,既没有达到既定目标,又写着低质量并难以维护的代码.在这里我想引用 Michael A. Jackson 的一句话: "程序优化守则第一条:别去做它.程序优化守则第二条(仅限于专业人员):别去做它,现在还不是时候." 在开始任何优化之前,我们要先认清问题的症结所在.

移动App性能测评与优化导读

前 言 Preface 写作背景 当前移动设备越来越多地涌现在我们日常生活中,像网络购物.充值缴费.新闻资讯.理财.团购.车辆保养等都可以通过移动设备来搞定.通过移动设备可以帮助人们更便捷高效地完成很多事,同时越来越多的需求也希望能通过移动设备来完成,这样也催生了很多工作机会,让IT技术人员能开发更多的App来满足不同用户的不同需求.相对于传统PC,移动设备有其自身的特点,如屏幕小.移动网络复杂且需要收费.电量有限等.因此,在完成用户一系列需求的背后,我们也面临一系列的问题.比如说,如何能保证开

PHP实现批量检测网站是否能够正常打开的方法_php技巧

本文实例讲述了PHP实现批量检测网站是否能够正常打开的方法.分享给大家供大家参考,具体如下: curl_setopt函数是php中一个重要的函数,它可以模仿用户的一些行为,如模仿用户登录,注册等等一些用户可操作的行为. <?php //设置最大执行时间是 120秒 ini_set('max_execution_time',120); function httpcode($url){ $ch = curl_init(); $timeout = 3; curl_setopt($ch,CURLOPT_