您的位置:首页 > 移动开发

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

2016-12-02 18:46 295 查看

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

TimeProfiler

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

Allocations

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

Leak

可以在上面区域的Leaks部分看到对应的时间点产生的溢出,选择后在下面区域的Statistics>AllocationSummary能够看到泄漏的对象,同样可以通过StackTrace查看到具体对应的代码区域。

开发时需要注意如何避免一些性能问题

NSDateFormatter

通过Instruments的检测会发现创建NSDateFormatter或者设置NSDateFormatter的属性的耗时总是排在前面,如何处理这个问题呢,比较推荐的是添加属性或者创建静态变量,这样能够使得创建初始化这个次数降到最低。还有就是可以直接用C,或者这个NSData的Category来解决https://github.com/samsoffes/sstoolkit/blob/master/SSToolkit/NSData%2BSSToolkitAdditions.m

UIImage

这里要主要是会影响内存的开销,需要权衡下imagedNamed和imageWithContentsOfFile,了解两者特性后,在只需要显示一次的图片用后者,这样会减少内存的消耗,但是页面显示会增加ImageIO的消耗,这个需要注意下。由于imageWithContentsOfFile不缓存,所以需要在每次页面显示前加载一次,这个IO的操作也是需要考虑权衡的一个点。

页面加载

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

优化首次加载时间

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

监控卡顿的方法

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

int32_t__CFRunLoopRun()
{
__CFRunLoopDoObservers(KCFRunLoopEntry);
do
{
__CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(kCFRunLoopBeforeSources);//这里开始到kCFRunLoopBeforeWaiting之间处理时间是感知卡顿的关键地方

__CFRunLoopDoBlocks();
__CFRunLoopDoSource0();//处理UI事件

//GCDdispatchmainqueue
CheckIfExistMessagesInMainDispatchQueue();

//休眠前
__CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);

//等待msg
mach_port_twakeUpPort=SleepAndWaitForWakingUpPorts();

//等待中

//休眠后,唤醒
__CFRunLoopDoObservers(kCFRunLoopAfterWaiting);

//定时器唤醒
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两个状态之间的超过了极限值和出现次数的场景,将堆栈dump下来,最后发到服务器做收集,通过堆栈能够找到对应出问题的那个方法。

staticvoidrunLoopObserverCallBack(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity,void*info)
{
MyClass*object=(__bridgeMyClass*)info;
object->activity=activity;
}

staticvoidrunLoopObserverCallBack(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity,void*info){
SMLagMonitor*lagMonitor=(__bridgeSMLagMonitor*)info;
lagMonitor->runLoopActivity=activity;

dispatch_semaphore_tsemaphore=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);//DispatchSemaphore保证同步
//创建一个观察者
CFRunLoopObserverContextcontext={0,(__bridgevoid*)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){
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){
//出现三次出结果
if(++timeoutCount<3){
continue;
}

//将堆栈信息上报服务器的代码放到这里

}//endactivity
}//endsemaphorewait
timeoutCount=0;
}//endwhile
});

}


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

堆栈dump的方法

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

staticints_fatal_signals[]={
SIGABRT,
SIGBUS,
SIGFPE,
SIGILL,
SIGSEGV,
SIGTRAP,
SIGTERM,
SIGKILL,
};

staticints_fatal_signal_num=sizeof(s_fatal_signals)/sizeof(s_fatal_signals[0]);

voidUncaughtExceptionHandler(NSException*exception){
NSArray*exceptionArray=[exceptioncallStackSymbols];//得到当前调用栈信息
NSString*exceptionReason=[exceptionreason];//非常重要,就是崩溃的原因
NSString*exceptionName=[exceptionname];//异常类型
}

voidSignalHandler(intcode)
{
NSLog(@"signalhandler=%d",code);
}

voidInitCrashReport()
{
//系统错误信号捕获
for(inti=0;i<s_fatal_signal_num;++i){
signal(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里堆栈里的内容

2016-03-2814:59:26.922HomePageTest[4803:201212]INFO:RevealServerstarted(ProtocolVersion25).
2016-03-2814:59:27.134HomePageTest[4803:201212]费时测试
2016-03-2814:59:29.262HomePageTest[4803:201212]费时测试
2016-03-2814:59:30.865HomePageTest[4803:201212]费时测试
2016-03-2814:59:32.115HomePageTest[4803:201212]费时测试
2016-03-2814:59:33.369HomePageTest[4803:201212]费时测试
2016-03-2814:59:34.832HomePageTest[4803:201212]费时测试
2016-03-2814:59:34.836HomePageTest[4803:201615]laghappen,detailbelow:
IncidentIdentifier:73BEF9D2-EBE3-49DF-B95B-7392635631A3
CrashReporterKey:TODO
HardwareModel: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)
CodeType:X86-64
ParentProcess:debugserver[4805]

Date/Time:2016-03-2806:59:34+0000
OSVersion:MacOSX9.2(15D21)
ReportVersion:104

ExceptionType:SIGTRAP
ExceptionCodes:TRAP_TRACEat0x10aee6f79
CrashedThread:2

Thread0:
0libsystem_kernel.dylib0x000000010ec6b206__semwait_signal+10
1libsystem_c.dylib0x000000010e9f2b9eusleep+54
2HomePageTest0x000000010aedf934-[TestTableViewconfigCell]+820
3HomePageTest0x000000010aee5c89-[testTableViewControllerobserveValueForKeyPath:ofObject:change:context:]+601
4Foundation0x000000010b9c2564NSKeyValueNotifyObserver+347
5Foundation0x000000010b9c178fNSKeyValueDidChange+466
6Foundation0x000000010b9bf003-[NSObject(NSKeyValueObservingPrivate)_changeValueForKey:key:key:usingBlock:]+1176
7Foundation0x000000010ba1d35f_NSSetObjectValueAndNotify+261
8HomePageTest0x000000010aec9c26-[DCTableViewtableView:cellForRowAtIndexPath:]+262
9UIKit0x000000010c872e43-[UITableView_createPreparedCellForGlobalRow:withIndexPath:willDisplay:]+766
10UIKit0x000000010c872f7b-[UITableView_createPreparedCellForGlobalRow:willDisplay:]+74
11UIKit0x000000010c847a39-[UITableView_updateVisibleCellsNow:isRecursive:]+2996
12UIKit0x000000010c87c01c-[UITableView_performWithCachedTraitCollection:]+92
13UIKit0x000000010c862edc-[UITableViewlayoutSubviews]+224
14UIKit0x000000010c7d04a3-[UIView(CALayerDelegate)layoutSublayersOfLayer:]+703
15QuartzCore0x000000010c49a59a-[CALayerlayoutSublayers]+146
16QuartzCore0x000000010c48ee70_ZN2CA5Layer16layout_if_neededEPNS_11TransactionE+366
17QuartzCore0x000000010c48ecee_ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE+24
18QuartzCore0x000000010c483475_ZN2CA7Context18commit_transactionEPNS_11TransactionE+277
19QuartzCore0x000000010c4b0c0a_ZN2CA11Transaction6commitEv+486
20QuartzCore0x000000010c4bf9f4_ZN2CA7Display11DisplayLink14dispatch_itemsEyyy+576
21CoreFoundation0x000000010e123c84__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__+20
22CoreFoundation0x000000010e123831__CFRunLoopDoTimer+1089
23CoreFoundation0x000000010e0e5241__CFRunLoopRun+1937
24CoreFoundation0x000000010e0e4828CFRunLoopRunSpecific+488
25GraphicsServices0x0000000110479ad2GSEventRunModal+161
26UIKit0x000000010c719610UIApplicationMain+171
27HomePageTest0x000000010aee0fdfmain+111
28libdyld.dylib0x000000010e92b92dstart+1

Thread1:
0libsystem_kernel.dylib0x000000010ec6bfdekevent64+10
1libdispatch.dylib0x000000010e8e6262_dispatch_mgr_init+0

Thread2Crashed:
0HomePageTest0x000000010b04a445-[PLCrashReportergenerateLiveReportWithThread:error:]+632
1HomePageTest0x000000010aee6f79__28-[SMLagMonitorbeginMonitor]_block_invoke+425
2libdispatch.dylib0x000000010e8d6e5d_dispatch_call_block_and_release+12
3libdispatch.dylib0x000000010e8f749b_dispatch_client_callout+8
4libdispatch.dylib0x000000010e8dfbef_dispatch_root_queue_drain+1829
5libdispatch.dylib0x000000010e8df4c5_dispatch_worker_thread3+111
6libsystem_pthread.dylib0x000000010ec2f68f_pthread_wqthread+1129
7libsystem_pthread.dylib0x000000010ec2d365start_wqthread+13

Thread3:
0libsystem_kernel.dylib0x000000010ec6b6de__workq_kernreturn+10
1libsystem_pthread.dylib0x000000010ec2d365start_wqthread+13

Thread4:
0libsystem_kernel.dylib0x000000010ec65386mach_msg_trap+10
1CoreFoundation0x000000010e0e5b64__CFRunLoopServiceMachPort+212
2CoreFoundation0x000000010e0e4fbf__CFRunLoopRun+1295
3CoreFoundation0x000000010e0e4828CFRunLoopRunSpecific+488
4WebCore0x0000000113408f65_ZL12RunWebThreadPv+469
5libsystem_pthread.dylib0x000000010ec2fc13_pthread_body+131
6libsystem_pthread.dylib0x000000010ec2fb90_pthread_body+0
7libsystem_pthread.dylib0x000000010ec2d375thread_start+13

Thread5:
0libsystem_kernel.dylib0x000000010ec6b6de__workq_kernreturn+10
1libsystem_pthread.dylib0x000000010ec2d365start_wqthread+13

Thread6:
0libsystem_kernel.dylib0x000000010ec6b6de__workq_kernreturn+10
1libsystem_pthread.dylib0x000000010ec2d365start_wqthread+13

Thread7:
0libsystem_kernel.dylib0x000000010ec6b6de__workq_kernreturn+10
1libsystem_pthread.dylib0x000000010ec2d365start_wqthread+13

Thread2crashedwithX86-64ThreadState:
rip:0x000000010b04a445rbp:0x0000700000093da0rsp:0x0000700000093b10rax:0x0000700000093b70
rbx:0x0000700000093cb0rcx:0x0000000000001003rdx:0x0000000000000000rdi:0x000000010b04a5ca
rsi:0x0000700000093b40r8:0x0000000000000014r9:0x0000000000000000r10:0x000000010ec65362
r11:0x0000000000000246r12:0x00007fdaf5800940r13:0x0000000000000000r14:0x0000000000000009
r15:0x0000700000093b90rflags:0x0000000000000202cs:0x000000000000002bfs:0x0000000000000000
gs:0x0000000000000000


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: