iOS开发-iOS应用性能调优的25个建议和技巧

我要给出的建议将分为三个不一样的等级: 入门级、 中级和进阶级:javascript

入门级(这是些你必定会常常用在你app开发中的建议)html

1. 用ARC管理内存java

2. 在正确的地方使用reuseIdentifierios

3. 尽量使Views不透明web

4. 避免庞大的XIB缓存

5. 不要block主线程服务器

6. 在Image Views中调整图片大小网络

7. 选择正确的Collection架构

8. 打开gzip压缩app

中级(这些是你可能在一些相对复杂状况下可能用到的)

9. 重用和延迟加载Views

10. Cache, Cache, 仍是Cache!

11. 权衡渲染方法

12. 处理内存警告

13. 重用大开销的对象

14. 使用Sprite Sheets

15. 避免反复处理数据

16. 选择正确的数据格式

17. 正确地设定Background Images

18. 减小使用Web特性

19. 设定Shadow Path

20. 优化你的Table View

21. 选择正确的数据存储选项

进阶级(这些建议只应该在你确信他们能够解决问题和驾轻就熟的状况下采用)

22. 加速启动时间

23. 使用Autorelease Pool

24. 选择是否缓存图片

25. 尽可能避免日期格式转换

无需赘述,让咱们进入正题吧~

初学者性能提高

这个部分致力于一些能提升性能的基本改变。但全部层次的开发者都有可能会从这个记录了一些被忽视的项目的小小的性能备忘录里得到一些提高。

1. 用ARC管理内存

ARC(Automatic Reference Counting, 自动引用计数)和iOS5一块儿发布,它避免了最多见的也就是常常是因为咱们忘记释放内存所形成的内存泄露。它自动为你管理retain和release的过程,因此你就没必要去手动干预了。

下面是你会常常用来去建立一个View的代码段:

UIView *view = [[UIView alloc] init];
 // ...
 [self.view addSubview:view];
 [view release];

忘掉代码段结尾的release简直像记得吃饭同样简单。而ARC会自动在底层为你作这些工做。

除了帮你避免内存泄露,ARC还能够帮你提升性能,它能保证释放掉再也不须要的对象的内存。这都啥年代了,你应该在你的全部项目里使用ARC!

这里有一些更多关于ARC的学习资源:

ARC固然不能为你排除全部内存泄露的可能性。因为阻塞, retain 周期, 管理不完善的CoreFoundation object(还有C结构)或者就是代码太烂依然能致使内存泄露。

这里有一篇很棒的介绍ARC不能作到以及咱们该怎么作的文章 http://conradstoll.com/blog/2013/1/19/blocks-operations-and-retain-cycles.html。

2. 在正确的地方使用 reuseIdentifier

ReuseIdentifier.jpg

一个开发中常见的错误就是没有给UITableViewCells, UICollectionViewCells,甚至是UITableViewHeaderFooterViews设置正确的reuseIdentifier。

为 了性能最优化,table view用 `tableView:cellForRowAtIndexPath:` 为rows分配cells的时候,它的数据应该重用自UITableViewCell。 一个table view维持一个队列的数据可重用的UITableViewCell对象。

不使用reuseIdentifier的话,每显示一行table view就不得不设置全新的cell。这对性能的影响但是至关大的,尤为会使app的滚动体验大打折扣。

自iOS6起,除了UICollectionView的cells和补充views,你也应该在header和footer views中使用reuseIdentifiers。

想要使用reuseIdentifiers的话,在一个table view中添加一个新的cell时在data source object中添加这个方法:

static NSString *CellIdentifier = @"Cell";
 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

这个方法把那些已经存在的cell从队列中排除,或者在必要时使用先前注册的nib或者class创造新的cell。若是没有可重用的cell,你也没有注册一个class或者nib的话,这个方法返回nil。

3.尽可能把views设置为不透明

若是你有不透明的Views,你应该设置它们的opaque属性为YES。

缘由是这会使系统用一个最优的方式渲染这些views。这个简单的属性在IB或者代码里均可以设定。

Apple的文档对于为图片设置不透明属性的描述是:

Opaque.jpg

(opaque)这个属性给渲染系统提供了一个如何处理这个view的提示。若是设为YES, 渲染系统就认为这个view是彻底不透明的,这使得渲染系统优化一些渲染过程和提升性能。若是设置为NO,渲染系统正常地和其它内容组成这个View。默认值是YES。

在相对比较静止的画面中,设置这个属性不会有太大影响。然而当这个view嵌在scroll view里边,或者是一个复杂动画的一部分,不设置这个属性的话会在很大程度上影响app的性能。

你能够在模拟器中用Debug\Color Blended Layers选项来发现哪些view没有被设置为opaque。目标就是,能设为opaque的就全设为opaque!

4. 避免过于庞大的XIB

iOS5中加入的Storyboards(分镜)正在快速取代XIB。然而XIB在一些场景中仍然颇有用。好比你的app须要适应iOS5以前的设备,或者你有一个自定义的可重用的view,你就不可避免地要用到他们。

若是你不得不XIB的话,使他们尽可能简单。尝试为每一个Controller配置一个单独的XIB,尽量把一个View Controller的view层次结构分散到单独的XIB中去。

FatXIB.jpg

须要注意的是,当你加载一个XIB的时候全部内容都被放在了内存里,包括任何图片。若是有一个不会即刻用到的view,你这就是在浪费宝贵的内存资源了。Storyboards就是另外一码事儿了,storyboard仅在须要时实例化一个view controller.

当家在XIB是,全部图片都被chache,若是你在作OS X开发的话,声音文件也是。Apple在相关文档中的记述是:

当 你加载一个引用了图片或者声音资源的nib时,nib加载代码会把图片和声音文件写进内存。在OS X中,图片和声音资源被缓存在named cache中以便未来用到时获取。在iOS中,仅图片资源会被存进named caches。取决于你所在的平台,使用NSImage 或UIImage 的`imageNamed:`方法来获取图片资源。

很明显,一样的事情也发生在storyboards中,但我并无找到任何支持这个结论的文档。若是你了解这个操做,写信给我!

想要了解更多关于storyboards的内容的话你能够看看 Matthijs Hollemans的Beginning Storyboards in iOS 5 Part 1 和 Part 2

5. 不要阻塞主线程

RainbowWheel.jpg

永远不要使主线程承担过多。由于UIKit在主线程上作全部工做,渲染,管理触摸反应,回应输入等都须要在它上面完成。

一直使用主线程的风险就是若是你的代码真的block了主线程,你的app会失去反应。这。。。正是在App Store中拿到一颗星的捷径 :]

大部分阻碍主进程的情形是你的app在作一些牵涉到读写外部资源的I/O操做,好比存储或者网络。

你可使用`NSURLConnection`异步地作网络操做:

+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler

或者使用像 AFNetworking这样的框架来异步地作这些操做。

若是你须要作其它类型的须要耗费巨大资源的操做(好比时间敏感的计算或者存储读写)那就用 Grand Central Dispatch,或者 NSOperation 和 NSOperationQueues.

下面代码是使用GCD的模板

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// switch to a background thread and perform your expensive operation
dispatch_async(dispatch_get_main_queue(), ^{
// switch back to the main thread to update your UI
});
});

发现代码中有一个嵌套的`dispatch_async`吗?这是由于任何UIKit相关的代码须要在主线程上进行。

若是你对 NSOperation 或者GCD 的细节感兴趣的话,看看Ray Wenderlich的 Multithreading and Grand Central Dispatch on iOS for Beginners, 还有 Soheil Azarpour 的 How To Use NSOperations and NSOperationQueues 教程。

6. 在Image Views中调整图片大小

ImageViews.jpg

若是要在`UIImageView`中显示一个来自bundle的图片,你应保证图片的大小和UIImageView的大小相同。在运行中缩放图片是很耗费资源的,特别是`UIImageView`嵌套在`UIScrollView`中的状况下。

若是图片是从远端服务加载的你不能控制图片大小,好比在下载前调整到合适大小的话,你能够在下载完成后,最好是用background thread,缩放一次,而后在UIImageView中使用缩放后的图片。

7. 选择正确的Collection

Collections.jpg

学会选择对业务场景最合适的类或者对象是写出能效高的代码的基础。当处理collections时这句话尤为正确。

Apple有一个 Collections Programming Topics 的文档详尽介绍了可用的classes间的差异和你该在哪些场景中使用它们。这对于任何使用collections的人来讲是一个必读的文档。

呵呵,我就知道你由于太长没看…这是一些常见collection的总结:

  • Arrays: 有序的一组值。使用index来lookup很快,使用value lookup很慢, 插入/删除很慢。

  • Dictionaries: 存储键值对。 用键来查找比较快。

  • Sets: 无序的一组值。用值来查找很快,插入/删除很快。

8. 打开gzip压缩

大量app依赖于远端资源和第三方API,你可能会开发一个须要从远端下载XML, JSON, HTML或者其它格式的app。

问题是咱们的目标是移动设备,所以你就不能期望网络情况有多好。一个用户如今还在edge网络,下一分钟可能就切换到了3G。不论什么场景,你确定不想让你的用户等太长时间。

减少文档的一个方式就是在服务端和你的app中打开gzip。这对于文字这种能有更高压缩率的数据来讲会有更显著的效用。

好消息是,iOS已经在NSURLConnection中默认支持了gzip压缩,固然AFNetworking这些基于它的框架亦然。像Google App Engine这些云服务提供者也已经支持了压缩输出。

若是你不知道如何利用Apache或者IIS(服务器)来打开gzip,能够读下这篇文章

中级性能提高

你确信你已经掌握了前述那些基础级的优化方案了吗?但实际状况是,有时一些解决方案并不像那些同样明显,它们每每严重依赖于你如何架构和书写你的app。下面的这些建议就是针对这些场景的。

9. 重用和延迟加载(lazy load) Views

更多的view意味着更多的渲染,也就是更多的CPU和内存消耗,对于那种嵌套了不少view在UIScrollView里边的app更是如此。

这里咱们用到的技巧就是模仿`UITableView`和`UICollectionView`的操做: 不要一次建立全部的subview,而是当须要时才建立,当它们完成了使命,把他们放进一个可重用的队列中。

这样的话你就只须要在滚动发生时建立你的views,避免了不划算的内存分配。

建立views的能效问题也适用于你app的其它方面。想象一下一个用户点击一个按钮的时候须要呈现一个view的场景。有两种实现方法:

  • 1. 建立并隐藏这个view当这个screen加载的时候,当须要时显示它;

  • 2. 当须要时才建立并展现。

每一个方案都有其优缺点。

用第一种方案的话由于你须要一开始就建立一个view并保持它直到再也不使用,这就会更加消耗内存。然而这也会使你的app操做更敏感由于当用户点击按钮的时候它只须要改变一下这个view的可见性。

第二种方案则相反-消耗更少内存,可是会在点击按钮的时候比第一种稍显卡顿。

10. Cache, Cache, 仍是Cache!

一个极好的原则就是,缓存所须要的,也就是那些不大可能改变可是须要常常读取的东西。

咱们能缓存些什么呢?一些选项是,远端服务器的响应,图片,甚至计算结果,好比UITableView的行高。

NSURLConnection默认会缓存资源在内存或者存储中根据它所加载的HTTP Headers。你甚至能够手动建立一个NSURLRequest而后使它只加载缓存的值。

下面是一个可用的代码段,你能够能够用它去为一个基本不会改变的图片建立一个NSURLRequest并缓存它:

+ (NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; // this will make sure the request always returns the cached image
request.HTTPShouldHandleCookies = NO;
request.HTTPShouldUsePipelining = YES;
[request addValue:@"image
}
}

这段代码在每次遍历后释放全部autorelease对象

更多关于NSAutoreleasePool请参考官方文档

24. 选择是否缓存图片

常见的从bundle中加载图片的方式有两种,一个是用`imageNamed`,二是用`imageWithContentsOfFile`,第一种比较常见一点。

既然有两种相似的方法来实现相同的目的,那么他们之间的差异是什么呢?

`imageNamed`的优势是当加载时会缓存图片。`imageNamed`的文档中这么说:

这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象若是它存在的话。若是缓存中没有找到相应的图片,这个方法从指定的文档中加载而后缓存并返回这个对象。

相反的,`imageWithContentsOfFile`仅加载图片。

下面的代码说明了这两种方法的用法:

UIImage *img = [UIImage imageNamed:@"myImage"]; // caching
 // or
 UIImage *img = [UIImage imageWithContentsOfFile:@"myImage"]; // no caching

那么咱们应该如何选择呢?

若是你要加载一个大图片并且是一次性使用,那么就不必缓存这个图片,用`imageWithContentsOfFile`足矣,这样不会浪费内存来缓存它。

然而,在图片反复重用的状况下`imageNamed`是一个好得多的选择。

25. 避免日期格式转换

若是你要用`NSDateFormatter`来处理不少日期格式,应该当心以待。就像先前提到的,任什么时候候重用`NSDateFormatters`都是一个好的实践。

然 而,若是你须要更多速度,那么直接用C是一个好的方案。Sam Soffes有一个不错的帖子(http://soff.es/how-to-drastically-improve-your-app-with- an-afternoon-and-instruments)里面有一些能够用来解析ISO-8601日期字符串的代码,简单重写一下就能够拿来用了。

嗯,直接用C来搞,看起来不错了,可是你相信吗,咱们还有更好的方案!

若是你能够控制你所处理的日期格式,尽可能选择Unix时间戳。你能够方便地从时间戳转换到NSDate:

- (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
 return [NSDate dateWithTimeIntervalSince1970:timestamp];
 }

这样会比用C来解析日期字符串还快!

须要注意的是,许多web API会以微秒的形式返回时间戳,由于这种格式在javascript中更方便使用。记住用`dateFromUnixTimestamp`以前除以1000就行了。

相关文章
相关标签/搜索