1 优化资源文件
在iOS本地资源文件编译后放置与应用程序包(Bundle)文件中即<应用名>.app文件。
NSBundle *bundle = [NSBundle mainBundle];
NSString *plistPath = [bundle pathForResource:@"team" ofType:@"plist"];
1.1 声音格式优化
1.1.1 iOS平台主要的音频文件格式
WAV文件,WAV文件格式是一种由微软和IBM联合开发的用于音频数字存储的标准,WAV文件的格式灵活,可以储存多种类型的音频数据。由于文件较大不太适合于移动设备这些存储容量小的设备。
MP3(MPEG Audio Layer 3)文件,是现在非常流行,MP3是一种有损压缩格式,它尽可能地去掉人耳无法感觉的部分和不敏感的部分。
CAFF(Core Audio File Format)文件,是苹果开发的专门用于Mac OSX和iOS系统无压缩音频格式。它被设计来替换老的WAV格式。
AIFF(Audio Interchange File Format)文件,是苹果开发的专门用于Mac OS X系统,是专业的音频文件格式。AIFF的压缩格式是AIFF-C(或AIFC),将数据以4:1压缩率进行压缩,应用于Mac OS X和iOS系统。
1.1.2 背景音乐优化
文件应该比较小,压缩文件是不错的选择,压缩文件主要是AIFC和MP3可以选择,没有特殊情况我们一定要首选AIFC格式,因为这是苹果推荐的格式。
原始文件格式不一定是AIFC,这种情况下我们需要使用afconvert工具转换为AIFC格式:
$ afconvert -f AIFC -d ima4 Fx08822_cast.wav
1.1.3 音乐特效优化
音乐特效很多应用游戏中,当发射子弹、敌人被打死和按钮点击等发出的声音,这些声音都是比较短的,
如果追求震撼的3D效果,可以采用苹果专用无压缩CAFF格式文件,其它格式的文件尽量不要考虑。
$ afconvert -f caff -d LEI16 Fx08822_cast.wav
1.2 图片格式优化
创建UIImage对象方法的优化
imageNamed:类级构造方法,方法会在内存中建立缓存,这些缓存直到应用停止才清除,如果是贯穿于整个应用的图片(如图:图标、logo等)推荐使用。
-initWithContentsOfFile: 实例构造方法,如果是使用一次就基本上不再使用的图片推荐使用该方法。
NSString *path = [[NSBundle mainBundle] pathForResource:@"animal-2" ofType:@"png"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
... ...
[image release];
// MRR情况下调⽤用
1.3 图片裁切
1.3.1 UIImage自定义绘制的四种方法
///方法中会自动做缩放处理
(void) getBitmapImage:(UIImage *)image Size:(CGSize)imageSize WithCompletionBlock:(HJCallbackBlock)block
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
if (!context) {
dispatch_async(dispatch_get_main_queue(), ^{
block(image);
});
}
CGRect rect = CGRectMake(0, 0, imageSize.width, imageSize.height);
//坐标系统已经自动考虑了缩放因素,不需要额外处理
[image drawInRect:rect blendMode:kCGBlendModeNormal alpha:1];
UIImage *temp = UIGraphicsGetImageFromCurrentImageContext();
NSData *tempData = UIImageJPEGRepresentation(temp, 1);
UIGraphicsEndImageContext();
//设置SDWebImage库的缓存
NSString *device = [MDUtility getCurrentDeviceModel];
if ([device rangeOfString:@"iPhone 4"].length > 0) {
if (tempData.length > 500000) {
tempData = UIImageJPEGRepresentation(temp, 0.4);
}
temp = [UIImage imageWithData:tempData];
}
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block(temp);
}
});
});
// //改进方案1
// CGImageRef imgRef = CGImageCreateWithImageInRect(image.CGImage,CGRectMake(0, 0, image.size.width, image.size.height));
// UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0);
// CGContextRef context = UIGraphicsGetCurrentContext();
// CGContextDrawImage(context, imageRect, imgRef);
// UIImage *imgData = UIGraphicsGetImageFromCurrentImageContext();
// UIGraphicsEndImageContext();
// CGImageRelease(imgRef);
// UIImage *data = [self verticallyFlipImage: imgData];
// return data;
//方案二,内存有释放,挂机
// UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
// CGContextRef context = UIGraphicsGetCurrentContext();
// CGRect rect = CGRectMake(0, 0, imageSize.width * [UIScreen mainScreen].scale, imageSize.height * [UIScreen mainScreen].scale);
// // draw alpha-mask
// CGContextDrawImage(context, rect, image.CGImage);
// // draw tint color, preserving alpha values of original image
// CGContextFillRect(context, rect);
//
// //Set the original greyscale template as the overlay of the new image
// UIImage *imgData = [self verticallyFlipImage:image];
// [imgData drawInRect:imageRect];
// UIImage *colouredImage = UIGraphicsGetImageFromCurrentImageContext();
// UIGraphicsEndImageContext();
// CGContextRelease(context);
//
// return colouredImage;
//方案三,CGBitmapContextCreate方案,内存没释放
// CGFloat targetWidth = imageSize.width * [UIScreen mainScreen].scale;
// CGFloat targetHeight = imageSize.height * [UIScreen mainScreen].scale;
// CGImageRef imageRef = [image CGImage];
// CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
// CGColorSpaceRef colorSpaceInfo = CGImageGetColorSpace(imageRef);
// CGContextRef bitmapContext;
// bitmapContext = CGBitmapContextCreate(NULL, targetWidth, targetHeight,CGImageGetBitsPerComponent(imageRef),CGImageGetBytesPerRow(imageRef), colorSpaceInfo, bitmapInfo);
// CGContextDrawImage(bitmapContext, CGRectMake(0, 0, targetWidth, targetHeight), imageRef);
// CGImageRef imgref = CGBitmapContextCreateImage(bitmapContext);
// UIImage* newImage = [UIImage imageWithCGImage: imgref];
// CGColorSpaceRelease(colorSpaceInfo);
// CGContextRelease(bitmapContext);
// CGImageRelease(imgref);
// return newImage;
//方案四,CGBitmapContextCreate方案,但是采用CGDataProviderCreateWithCFData方案解决内存占用问题
// NSData *data = UIImageJPEGRepresentation(image, 1);
// CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
// CGImageRef imageRef = CGImageCreateWithJPEGDataProvider(dataProvider,
// NULL, NO,
// kCGRenderingIntentDefault);
//
// CGFloat targetWidth = imageSize.width * [UIScreen mainScreen].scale;
// CGFloat targetHeight = imageSize.height * [UIScreen mainScreen].scale;
// // CGImageRef imageRef = [image CGImage];
//
// CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
//
// CGColorSpaceRef colorSpaceInfo = CGImageGetColorSpace(imageRef);
// CGContextRef bitmapContext;
// bitmapContext = CGBitmapContextCreate(NULL, targetWidth, targetHeight,CGImageGetBitsPerComponent(imageRef),0, colorSpaceInfo, bitmapInfo);
// CGContextDrawImage(bitmapContext, CGRectMake(0, 0, targetWidth, targetHeight), imageRef);
//
// // If failed, return undecompressed image
// if (!bitmapContext) return image;
//
// CGImageRef imgref = CGBitmapContextCreateImage(bitmapContext);
// UIImage* newImage = [UIImage imageWithCGImage:imgref];//[UIImage imageWithCGImage:decompressedImageRef scale:image.scale orientation:image.imageOrientation];
//
// CGColorSpaceRelease(colorSpaceInfo);
// CGContextRelease(bitmapContext);
// CGImageRelease(imgref);
//
// return newImage;
}
2 延迟加载
2.1 资源文件的延迟加载
非延迟加载方式
延迟加载方式
2.2 故事板和nib文件的延迟加载
2.2.1 故事板的延迟加载
Segue定义的两个视图控制器的导航关系,也来维护和管理下一个视图控制器的延迟加载时机,这种情况下我们无法“插手”视图控制器的延迟加载。但是一种情况下除外,就是使用了故事板,而控制器之间没有定义导航关系,没有定义Segue。
2.2.2 nib文件延迟加载
相当于故事板而言nib要灵活的很多,nib文件有两种:一种是描述视图控制器的,另一种是描述视图的,加载方式有所区别。
3 数据持久化的优化
文件
SQLite数据库
CoreData
3.1 使用文件
l 避免多次写入很少的数据,最好是当数据积攒的一定数量,一次写入。
l 将文件读写访问从主线程中剥离出来,由一个子线程负责。
l 写入应该采用增量方式,每次只写入变化的部分,不要为改变几个字节
l 写入整个文件。
3.1.1 文件结构优化
文件要保存数据,应该是结构化的,苹果中的plist文件就是很好的结构化文件。plist文件结构是层次模型的树形结构,层次的深浅会影响读取/写入的速度。
3.1.2 文件大小优化
l dataWithPropertyList: format: options: error: 按照指定的格式和操作参数,序列化属性列表对象到NSData对象。
l propertyListWithData: options: format: error: 按照指定的格式和操作参数,从NSData对象反序列化到属性列表对象中。
3.2 使用SQLite数据库
3.2.1 表结构优化
在iOS这些CPU处理能力低、内存少、存储空间少情况下,我们不能在本地建立复杂表关系,表的个数也不宜超过5个,表中的字段数量也不宜太多。
移动设备中的数据是不可能是企业级系统数据的全部,它只是企业级系统的补充和扩展。
3.2.2 查询优化
3.2.2.1 索引
索引能够提供查询性能,哪些字段需要创建索引很关键,这些字段只有在表连接或where条件子句中使用才能提供查询性能;在INTEGER PRIMARY KEY字段上不用建索引,表中数据很少情况下建索引效果不大。
3.2.2.2 限制返回记录数
在限制返回记录数方面,由于移动设备屏幕相当比较小,屏幕上能显示的数据不多,一次查询出记录数,超过屏幕显示能显示行数,这就没有必须了,也会占用更多的内存、耗费宝贵的CPU时间。因此我们需要为查询添加返回记录数的限制,下面语句是SQLite支持的写法:
SELECT * FROM Note Limit 10 Offset 5;
3.2.2.3 where条件子句
尽量不用使用Like模糊匹配查询,如果可能则使用“=”号查询。还有尽量不要使用IN语句,可以使用“=”号和or替。还有多个条件中要把非文本的条件放在前面,文本条件放在后面,如下代码:
(salary > 5000000) AND (lastName LIKE 'Guan') 优于 (lastName LIKE 'Guan')AND (salary > 5000000)
3.2.3 插入(或删除)优化
关闭数据同步 PRAGMA synchronous = OFF,插入完成也可以设置回来PRAGMA synchronous =NORMAL或PRAGMA synchronous = FULL。
在Objective-C可以调用函数sqlite3_exec实现设置,语句如下:
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &err);
3.3 使用CoreData
3.3.1 使用存储类型NSSQLiteStoreType
CoreData的存储类型有NSSQLiteStoreType、NSBinaryStoreType和NSInMemoryStoreType。其中我们注意采用NSSQLiteStoreType类型,这样底层存储就采用了SQLite数据库,SQLite数据库的优点也能发挥出来。
3.3.2 查询优化
它的查询是通过NSFetchRequest执行Predicate定义的逻辑查询条件实现的,优化规则上与SQLite的where条件子句是一样的。此外,查询返回记录数的限制,可以使用语句:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
//限制⼀一次提取记录数
[request setFetchLimit:10];
//限制提取记录偏移量
[request setFetchOffset:5];
3.3.3 设置PRAGMA指令
3.3.4 Instruments工具中CoreData跟踪模板
4 可重用对象的使用
l 表视图(UITableView)
l 集合视图(UICollectionView)
l 地图视图(MKMapView)
4.1 表视图中的重用对象
4.1.1 表视图单元格
dequeueReusableCellWithIdentifier:和 dequeueReusableCellWithIdentifier:forIndexPath:
dequeueReusableCellWithIdentifier: 方法通过可以中标识符从表视图中获得可重用单元格,模式代码如下。
4.1.2 表视图节头脚视图
使用表视图的dequeueReusableHeaderFooterViewWithIdentifier:方法获得UITableViewHeaderFooterView对象,如果没有可重用的UITableViewHeaderFooterView对象,则使用initWithReuseIdentifier:构造方法创建。模式代码如下:
4.2 集合视图中的重用对象
4.2.1 单元格视图
4.2.2 补充视图
4.3 地图视图中的重用对象
4.3.1 MKPinAnnotationView对象
5 并发处理与多核CPU
5.1 主线程阻塞问题
ViewController.m中的click:方法
6 编译器和编译参数
6.1 GCC、LLVM GCC与Apple LLVM比较
l GCC(GNU Compiler Collection,GNU编译器套装),是一套由 GNU 开发的编程语言编译器。也是Linux、Unix及Mac OS X 操作系统的标准编译器,GCC可以编译C、C 、Objective-C、Java和Pascal等语言。
l LLVM(Low Level Virtual Machine,低级虚拟机),这个虚拟机提供了一套中立的中间代码和编译基础设施,并围绕这些设施提供了一套全新的编译策略(使得优化能够在编译、连接、运行环境执行过。LLVM GCC是 LLVM下编译C、C 和Objective-C编译器。
l Apple LLVM,是苹果LLVM编译器,2005年开始称为了苹果官方支持的编译器。2010 WWDC(Worldwide Developers Conference,苹果电脑全球研发者大会),苹果公司报告LLVM编译器比GCC编译器快60%。在Xcode 4之后默认采用Apple LLVM编译器。
6.2 Optimization Level
Optimization Level有5个级别
l -O0,是默认级别,不进行任何的优化,直接将源代码编译到执行文件中,结果不进行任何的重排,编译时间比较长。主要用于调试程序,可以设置断点、改变变量、计算表达式等调试工作。
l -O1(或-O),是最常用的优化级别,不考虑速度和文件大小权衡问题,与-O0级别相比生成文件更小,可执行的速度更快,编译时间更少。
l -O2,是在-O1级别基础上再进行优化,增加的指令调度的优化,与-O1级别相比生成文件大小没有变大,编译时间变长了,编译期间占用内存更多了,但程序的运行速度有所提高。该级别是应用程序发布时候的最理想级别,在增加文件大小的情况下提供了最大优化。
l -O3,是在-O2和-O1级别上再进行优化,该级别可能会提高程序的运行速度,但是也会增加文件的大小。
l -Os,该种级别用于在有限的内存和磁盘空间下生成尽可能小的文件,由于使用了很好的缓存技术,在某些情况下也会有很快的运行速度。
7 参考资料
iOS优化(一)内存优化经验
http://www.jianshu.com/p/ef52250df748