在代码优化之前需要找到代码中存在的问题以及造成这种问题的根本原因。但是不要陷入过度优化的陷阱。本章我们会介绍几个高性能评估的几个关键参数。
参数及测量
为了提高性能,我们必须首先找出我们所要评估的问题。如果没有可以提升的话,那么也就没有什么可以优化的问题。
以应用为基础——该应用是用来做什么以及怎么做,这些参数中的一个或者多个能够适用于该应用。
内存
说道内存,所指的是应用所耗费的RAM。应用有三个部分的内存消耗。
堆栈的大小
应用的新线程的堆栈控件大小包括预留空间以及耗费的内存。当退出线程的时候会放空堆空间。线程的最大堆空间大小是很有限的:
- 可以递归调用的方法数量上限。每个方法都有自己的堆栈帧,构成消耗的堆栈空间。所以,如果调用顺序是main 函数——方法1——方法2,那么这三个方法都会占用空间。图1 所表示的是线程堆栈随时间的变化。
- 方法中使用的变量的个数。所有的变量都从方法的堆栈上进行加载。
- 层级中view的数量。view的渲染会调用layoutSubViews以及drawRect方法。如果层次很深的话比较容易造成堆栈溢出现象。
堆大小——Heap size
一个进程的所有线程享有同一个堆。一个应用可用的空间比设备的RAM要小很多。例如,iPhone 5S有1GB的RAM,但是每个应用至多可以有512M的内存甚至更少。
使用NSString、加载图片、创建或者解析JSON/XML数据以及view的使用都会产生堆空间的消耗。如果你的应用侧重图片的话——也就是说你的应用是一个图片处理类应用——你需要更加注重内存的消耗。
图2 展示了在应用中会存在的一种典型的堆
heap.png对象的寿命和泄露——Object longevity and leaks
对象在内存中存在的时间越长,对象所产生的内存不被清除的可能性就越高,所以要尽量避免让对象长久的存在在内存中。所以需要细心检查关键代码,因为你也不想每次都重新再次创建同一个对象。
应用中存在内存中最长时间的应该是单例对象,他们只创建一次却从不被销毁。
另一种就是全局变量。在高级编程语言当中,全局变量是最可怕的。
随着对象图变得越来越复杂,内存被清理的机会越来越渺茫,最可能产生的后果就是越来越频繁的内存崩溃。如果一个APP中有多条类似出具和网络操作的线程,并且他们需要同步执行的话,那么主线程只能等待其它地方所触发的线程执行完毕了才能响应用户的其它操作。
响应时间
表示的是对于用户操作的响应时间。例如,在用户点击了刷新按钮之后,APP发送请求,用一些动画来表示请求进度的这个时间应该尽量缩短。
执行时间
一个关键的评估指标,代码中的关键算法的执行时间。多次执行和评估能够有助于更好的了解代码的有效性以及数据结构是否使用的比较好。
网络
只要你的应用中涉及到了网络,也需要评估网络的使用。检测当一则广告出现时候的极端情况下网络的使用情况。一般的需要检测:
- 传输的字节数
- 平均以及最大带宽
耗电量
任何硬件功能都会消耗电池电量。消耗电量的主要活动有:
- 密集的任务,如图片处理或者滚动
- 使用像照相机、蓝牙以及话筒等硬件
- 通过推送通知唤醒应用
- 后台抓取
磁盘存储
评估应用所消耗的磁盘存储空间:
- 应用的二进制大小
- 文件的创建或者存储
- 持续的缓存
- 本地数据库存储
崩溃
这个是对应用性能估计的一个重要因素。不管因为什么原因导致了最终的崩溃,一旦应用崩溃了就说明性能有待优化。评估崩溃主要从发生崩溃的原因、类型以及频次来考虑。
崩溃报告
崩溃报告系统可以使我们及时知道崩溃的发生。所有的报告系统都会从服务器加载崩溃日志。以帮助分析崩溃产生的原因以及如何修正错误。
有许多可用的报告系统,而本文采用的是Flurry。而原因就是我可以像instrumentation一样,在即使只使用一个SDK的情况下设置崩溃报告。
可以在Flurry的官网上注册一个账号然后获得一个API key,并且下载该SDK。
#import "Flurry.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[Flurry setCrashReportingEnabled:YES];
[Flurry startSession:@"API_KEY"];
}
在这里,可以将代码中的API_KEY换成自己的项目的。
Instrumentation
Instrumentation不仅仅在了解用户行为方面,而且在辨认应用的关键路径方面都具有重要作用。有意注入代码来标注关键指标是改善应用性能的关键一步。
在应用的生命周期的四个部分都会使用Instrumentation:
- 在应用到前台,也就是获得焦点的时候 applicationDidBecomeActive:
- 当应用处于后台applicationDidEnterBackground:的时候
- 在应用受到内存警告的时候applicationDidReceiveMemoryWarning:
- 仅仅是为了有趣,我们在HPVFirstViewController添加一个按钮,并且在点击这个按钮的时候会让应用崩溃。
- (void)applicationDidBecomeActive:(UIApplication *)application {
[Flurry logEvent:@"App_Activate"];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
[Flurry logEvent:@"App_Background"];
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
[Flurry logEvent:@"App_MemWarn"];
}
图3表示的是添加一个按钮
崩溃按钮.pngHPVFirstViewController.m代码
- (IBAction)crashButtonWasClicked:(id)sender {
[NSException raise:@"Crash Button was Clicked" format:@""];
}
现在让我们多操作几次应用的启动、后台、前台并且点击后来添加的那个崩溃按钮。
再次启动该应用,这时候就会从服务器返回崩溃报告了。
图4 展示的是用户行为报告
Report of user sessions.png图5 是更加重要的应用的事件报告
Events - more important report.png图6 是最重要的崩溃报告
Crash Report - most important report.png点击途中红圈标注的Download按钮,可以下载崩溃日志。
日志
日志是可以知道应用所发生的操作的非常重要的工具,严格的说来,它与instrumentation还是有一些细微的差别的,这些差别主要体现在:
- 日志是本地的,但是instrumentation是实时的将数据传递到服务器进行分析
- instrumentation通常都被谨慎而少量的使用,而日志则通常被密集的使用。因为instrumentation发送大量的设备数据会消耗一些带宽。
- 日志通常会有详细的、调试、警告以及错误信息等,但是在instrumentation会先找到最佳的优化点然后再继续。
我建议instrumentation辅助日志记录,因为我认为,只要是能被instrumentation的也可以用日志达到。
现在将用NSLog来输出日志并且向服务器传递细节。
//HPInstrumentation.h
@interface HPInstrumentation : NSObject +(void)logEvent:(NSString *)name;
+(void)logEvent:(NSString *)name withParams:(NSDictionary *)params;
@end
//HPInstrumentation.m
@implementation HPInstrumentation
+(void)logEvent:(NSString *)name {
NSLog(@"%@", name);
[Flurry logEvent:name];
}
+(void)logEvent:(NSString *)name withParams:(NSDictionary *)params {
NSLog(@"%@ -> %@", name, params);
[Flurry logEvent:name withParameters:params];
}
@end
此外,我们还通过HPLogger来输出日志。首先,它仅仅是NSLog来输出日志。以后,我们可以使日志输出到文件或者是内存当中并且完整的展示到应用界面。日志级别可以在应用启动的时候只设置一次。
HPLogger类的代码如下所示:
//HPLogger.m
@interface HPLogger : NSObject +(int) logLevel;
+(void) setLogLevel:(int) level;
+(void)v:(NSString *) message; +(void)d:(NSString *) message; +(void)i:(NSString *) message; +(void)w:(NSString *) message; +(void)e:(NSString *) message;
@end
//HPLogger.m
static const int LEVEL_VERBOSE = 1; static const int LEVEL_DEBUG = 2; static const int LEVEL_INFO = 3; static const int LEVEL_WARNING = 4; static const int LEVEL_ERROR = 5;
@implementation HPLogger
static int _level = LEVEL_VERBOSE;
+(int) logLevel {
@synchronized(self) { return _level;
} }
+(void) setLogLevel:(int) level {
@synchronized(self) { _level = level;
} }
+(void)v:(NSString *) message {
if(_level <= LEVEL_VERBOSE) { NSLog(@"[V] %@", message);
} }
+(void)d:(NSString *) message {
if(_level <= LEVEL_DEBUG) { NSLog(@"[D] %@", message);
} }
+(void)i:(NSString *) message {
if(_level <= LEVEL_INFO) { NSLog(@"[I] %@", message);
} }
+(void)w:(NSString *) message {
if(_level <= LEVEL_WARNING) { NSLog(@"[W] %@", message);
} }
+(void)e:(NSString *) message {
if(_level <= LEVEL_ERROR) { NSLog(@"[E] %@", message);
} }
@end
网友评论