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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容