首先如果遇到应用卡顿或者因为内存占用过多时一般使用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的大概的逻辑

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下来,最后发到服务器做收集,通过堆栈能够找到对应出问题的那个方法。

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

 

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

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

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

  2. Android App性能自动化评测方法

    前言 App运行在设备上的性能表现也是质量保障的一个重要环节.因此,当我们确保了基本功能的准确之后,还需要有一定的方法评测App在不同设备上的性能表现.本文将从性能指标,评测方法,自动化体系建设等三个 ...

  3. ios 判断app程序第一次启动方法

    if(![[NSUserDefaults standardUserDefaults] boolForKey:@"firstStart"]){ [[NSUserDefaults st ...

  4. Android APP性能分析方法及工具

    近期读到<Speed up your app>一文.这是一篇关于Android APP性能分析.优化的文章.在这篇文章中,作者介绍他的APP分析优化规则.使用的工具和方法.我觉得值得大家借 ...

  5. 25条提高iOS App性能的建议和技巧

    这篇文章来自iOS Tutorial Team 成员 Marcelo Fabri, 他是 Movile 的一个iOS开发者. Check out his personal website or fol ...

  6. [转]iOS hybrid App 的实现原理及性能监测

    转自:http://www.cocoachina.com/ios/20151118/14270.html iOS hybrid App 的实现原理及性能监测 2015-11-18 11:39 编辑:  ...

  7. iOS App 性能优化总结

    今天简单总结一些clientapp 优化的方案和方向. 我相信开发一个app大部分团队都能够完毕,可是性能久不一样啦,和我们都写一个冒泡算法一样,我相信每一个人写的冒泡算法都不一样,这些区别就带来了性 ...

  8. IOS开发-提升app性能的25条建议和技巧

    前言 这篇文章介绍了作者开发工作中总结的25个iOS开发tips, 多年之前读过这篇文章.收益良多,基本每一个tips在我的应用开发过程中都使用过.今天把这篇文章又一次整理转发下,与大家一起学习,不论 ...

  9. 25条提高iOS App性能的技巧和诀窍

    25条提高iOS App性能的技巧和诀窍 当我们开发iOS应用时,好的性能对我们的App来说是很重要的.你的用户也希望如此,但是如果你的app表现的反应迟钝或者很慢也会伤害到你的审核. 然而,由于IO ...

随机推荐

  1. poj 3353 Road Construction tarjan 边双联通分支 缩点+结论

    题意:一个连通的无向图,求至少需要添加几条边,救能保证删除任意一条边,图仍然是连通的. 链接:http://poj.org/problem?id=3352 思路:边的双连通图.其实就是要求至少添加几条 ...

  2. BZOJ 3694 最短路

    233333想简单了.... 题解参见http://hzwer.com/3710.html #include<iostream> #include<cstdio> #inclu ...

  3. 20160129.CCPP体系详解(0008天)

    程序片段(01):函数.c+call.c+测试.cpp 内容概要:函数 ///函数.c #include <stdio.h> #include <stdlib.h> //01. ...

  4. MySQL Database on Azure - 利用PowerShell快速创建使用数据库服务

    如果您的应用需要通过脚本快速创建一个或多个MySQL数据库,MySQL Database on Azure目前开放了对于PowerShell的支持,可以帮助您快速利用自动化脚本进行数据库服务的创建.管 ...

  5. org.hibernate.MappingException: duplicate import异常

    在开发hibernate时,一起多谢ORM类和映射文件时,报出:org.hibernate.MappingException: duplicate import com.XXX异常 解决方案: 检查你 ...

  6. POI取消科学计数法

    前台输入手机号13777777777,如果是为Double类型接收,就会自动转为科学计数法 找了下,一般是Double转String,方法一般有两种: 1.利用String.format() sale ...

  7. FastJson只序列化java对象的部分属性

    public class Student { private int id; private String name; private int age; //get set方法略 } 如下方法: St ...

  8. Yii: 设置数据翻页

    一种方法是使用CPagination处理翻页需要的数据如:总数据项数,每页数据项数,当前页,然后在视图中使用CBasePager来绘制. 控制器动作的代码示范: function actionInde ...

  9. OS X中如何获取当前运行程序的路径

    OS X的执行程序一般分为两种.一种是控制台程序,一种是带有GUI的OS X应用程序. 控制台程序往往就一个文件构成executable,而GUI应用通常是一个包(即文件夹),里面除了executab ...

  10. simpletest:一个简单的PHP试工具

    1. 下载: http://simpletest.org/ .2. 下载后,只要测试文件中包含如下两个文件,就可以用了 : <?php require_once('simpletest/auto ...