【IOS开发进阶系列】APP性能优化专题

2023-10-16 11:36:25 浏览数 (1)

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

0 人点赞