iOS 提升应用性能小技巧

1,035 阅读13分钟

阅读原文

在开发 iOS 应用程序时,确保应用程序具有良好的性能是至关重要的。然而,由于开发周期的局限性,让我们很容易忘记决策对性能的影响。这篇文章整理了一些提升性能的小技巧,希望会对你有所帮助。

初级技巧

使用 ARC 来管理内存

ARC 自动引用计数,会自动为代码在合适的位置添加ratain/release,你不必手动来管理它。这样,就消除了最常见的内存泄漏问题。

除了帮助你避免内存泄漏之外,ARC 还可以通过确保对象在不在需要时立即释放来提升性能。

值得注意的是,ARC 并不能消除所有的内存泄漏。即使使用 ARC ,仍可能会出现内存泄漏,这主要是由于块、引用循环、对 CoreFoundation 对象管理不善或糟糕的代码引起的。

在适当的地方使用重用标识

在适当的地方使用重用标识,如:UITableViewCells、UICollectionViewCells、甚至是UITableViewHeaderFooterViews。

如果不使用重用标识,表视图在每次显示行的时,会配置一个一个全新的单元格,这是一个昂贵的操作,会影响滚动性能。

在iOS 6之后,你也需要为页眉和页脚视图,以及 UICollectionView 的单元格和补充视图使用重用标识。

尽可能将视图设置为不透明

可以通过将 opaque 属性设置为 YES 来设置,这个属性默认是 YES。

该属性会向绘图系统提供有关如何处理视图的提示。如果设置为 YES , 绘图系统会将视图视为完全不透明,从而允许绘图系统优化绘图操作,从而提升性能。

可以使用 Debug\Color Blended Layers 选项来查看哪些视图未设置为不透明。

避免使用臃肿的 XIBs

如果必须要使用 XIBs ,要尽可能使它们简单。当将xib加载到内存时,它所有的内容都会加载到内存中,包括图像。如果你没有立即使用它,那么就会浪费宝贵的内存。

不要阻塞主线程

永远不要在主线程上执行繁重的操作,因为 UIKit 在主线程上执行自己所有的工作,这样会使页面发生卡顿,严重影响用户体验。

大部分阻塞主线程的情况发生在执行I/O操作时,因为该操作需要从外部资源(如:磁盘、网络)读取或写入。

如果需要执行昂贵的操作,可以使用 GCD 或 NSOperations 和 NSOperationQueues。

根据图像视图大小来调整图像大小

如果使用 UIImageView 来显示图像,要确保图像和 UIImageView 的大小相同。动态缩放图像会非常昂贵,特别是将 UIImageView 嵌入到 UIScrollView 中。

如果是从远程服务器下载的图像,有时可能无法控制大小。那么,可以在图像下载完成后,在后台线程中手动缩放图像,然后在 UIImageView中使用调整过大小的图像。

正确选择集合类

学习使用最合适的类或对象来完成手头的任务是编写高效代码的基础。在处理集合时更是如此。一下时常见集合类型的简要说明:

  • Arrays:有序列表,按索引查找速度快,按值查找速度慢,插入删除速度慢。
  • Dictionaries:存储键值对儿。按键进行快速查找。
  • Sets:无序列表。按值快速查找,快速插入/删除。

启用 gzip 压缩

大部分应用程序都依赖于来自远程服务器或其他外部 api 的外部数据。在某个时候,您的应用程序,需要下载XML、JSON、HTML或其他文本格式的数据。

问题是,当涉及到移动设备时,不能依赖网络状况。用户可以在一分钟内到达边缘网络,然后进入3G网络。不管是什么情况,你都不想让你的用户等待!

减少文件大小和加快基于网络的资源下载的一个选项是在服务器和客户端上都启用 gzip 压缩。这对于基于文本的数据尤其有用,因为它具有很高的潜在压缩比。

中级技巧

重用和懒加载视图

更多的视图意味着更多的绘制,这意味着更多的 CPU 和内存开销。如果在 UIScrollView 中嵌入了很多视图,这一点尤为重要。

管理它的技巧时模仿 UITableView 和 UICollectionView 的行为:不要一次创建索引子视图,而是根据需要创建视图,并在完成时将其添加到重用队列中。这样,您只需要在执行滚动时配置视图,从而避免分配成本—这可能会很昂贵。

创建视图的时机至少有以下两种方法:

  • 在页面第一次加载时创建视图并将其因此,在需要时在显示该视图。
  • 在需要时创建并显示视图。

这两种方法各有利弊:

第一种方法会消耗更多的内存,因为会立即创建一个视图,该视图在释放之前一直保留在内存中。但是,当需要显示该视图时,因为只需要更改视图可见性,因此会更快显示出视图。

第二张方法会是相反的效果。只在需要时创建视图,会消耗更少的内存。但是,在需要显示时,视图显示不会那么及时。

缓存

在开发应用程序时,一个很好的经验法则是“缓存重要的东西”,也就是那些不太可能改变但经常访问的东西。如:UITableViewCell 的行高,远程服务器的响应等。

考虑使用绘制

在 iOS 中,有几种方法可以制作漂亮的按钮。可以使用全尺寸图像,可调整大小的图像,也可以使用 CALayer、CoreGraphics设置OpenGL手动绘制。

使用图片会更快,因为不必创建图像并在其上绘制形状,就可以最终显示到屏幕上。问题是需要将这些图片放入到项目中,会增加包大小。

考虑什么对你最重要:绘制性能或包大小,然后选择合适的方案。

处理内存警告

当系统内存不足时,iOS 会通知所有正在运行的应用程序。如果你的应用收到此警告,它必须释放尽可能多的内存。最好的方式是:移除缓存,图像对象,和稍后可重新创建的数据对象的强引用。

UIKit 提供了几种接收低内存警告的方法:

  • 实现应用程序的 applicationDidReceiveMemoryWarning: 代理方法。
  • 自定义 UIViewController 的子类中,重写 didReceiveMemoryWarning方法。
  • 注册 UIApplicationDidReceiveMemoryWarningNotification 通知。

一旦收到内存警告,释放所有可能的内存是非常重要的。否则,您的应用程序可能会被系统杀死。

在开始剔除对象以释放内存时要小心,因为需要确保以后可以重新创建它们。在开发应用程序时,务必使用 iOS 模拟器上的模拟内存警告功能来测试此情况。

重用昂贵的对象

有些对象的初始化速度非常慢,如 NSDateFormatter 和 NSCalendar 。但是,你不能总是避免使用它们,例如在解析 json/xml 响应中的日期。

为了避免在使用这些对象时出现性能瓶颈,要尽可能重用这些对象。可以通过向类中添加属性或静态变量来完成此操作。

注意,如果你选择第二种方法,则当应用程序运行时,对象将保留在内存中,这与单例非常类似。

避免重新处理数据

大部分应用程序都需要从远程服务器获取数据,这些数据通常以 json 或 xml 的格式出现。在请求和接收时,在两端使用相同的数据结构是重要的。因为,操作内存中的数据以适应新的数据结构会是昂贵的。

例如,如果需要在表视图中显示数据,最好以数组格式请求和接收数据,以避免对数据进行任何中间操作,使其适合你需要的数据结构。

类似地,如果应用程序依赖于通过键访问特定值,那么你需要请求并接收字典。

选择正确的数据格式

有多种方法可以将数据从服务器传输到应用程序,但最常见的两种方法是 JSON 和 XML 。你要确保为你的应用选择正确的一个。

JSON 的解析速度更快,而且通常比 xml 小,这也意味着传输更少的数据。而且自从 iOS 5以来,就有了内置的 JSON 反序列化,因此也很容易使用。

XML 的一个有点是,如果使用 SAX 解析方法,可以在脱机状态下使用 XML 数据,而不必等到整个数据到达后在像 JSON 那样解析它。处理非常大的数据集时,这可以提高性能并减少内存消耗。

适当设置背景图

至少有两种方法可以将背景图像放置在视图中:

  • 可以将背景色设置为使用 UIColor 的 colorWithPatternImage: 方法创建的颜色。
  • 可以将 UIImageView 子视图添加到视图中。

如果您有一个全尺寸的背景图像,那么一定要使用 UIImageView ,因为 UIColor 的 colorWithPatternImage: 创建的是小图,而不是大尺寸图像。在这种情况下,使用 UIImageView 将节省大量内存。

如果视图背景使用较小的图像,这些图像将被重复或平铺以填充背景,您应该使用 UIColor 的 colorWithPatternImage: 代替,因为在这种情况下,绘制速度更快,而且不会占用大量内存。

设置阴影路径

当要给视图添加阴影时,大部分开发人员会向下面这样:

UIView *view = [[UIView alloc] init];
view.layer.shadowOffset = CGSizeMake(-1.0f, 1.0f);
view.layer.shadowRadius = 5.0f;
view.layer.shadowOpacity = 0.6;

这种方法是有问题的, Core Animation必须进行一次离屏操作,以确定视图的确切形状,然后才能渲染阴影,这个操作是非常昂贵的。

有一个系统更容易渲染的替代方案:设置阴影路径。

view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];

通过设置阴影路径,iOS 不需要重新计算它应该如何绘制阴影。相反,它会使用一个预先计算好的路径。坏消息是,根据你的视图样式,可能很难由你自己计算路径。另一个问题是,每次视图的帧发生更改时,都需要更新阴影路径。

优化表视图

要使表视图滚动顺畅,确保实现以下建议:

  • 通过设置正确的 reuseIdentifier 来重用单元格。
  • 使尽可能多的视图不透明,包括单元格本身。
  • 避免渐变、图像缩放和离屏渲染。
  • 缓存行的高度,如果它们不总是相同的话。
  • 如果单元格显示来自 Web 的内容,请确保异步调用并缓存响应。
  • 使用 shadowPath 来设置阴影。
  • 减少子视图数量。
  • 尽可能少的在 cellForRowAtIndexPath: 中工作。如果你需要做一些工作,只做一次并缓存结果。
  • 使用适当的数据结构保存所需的信息。不同的结构对不同的操作有不同的成本。
  • 使用 rowHeight 、sectionFooterHeight 和 sectionHeaderHeight 来设置恒定高度,而不是通过询问代理获得。

选择正确的数据存储选项

在存储和读取大型数据时,会有如下选择:

  • 使用 NSUserDefaults。
  • 以 XML 、 JSON 或 Plist 的格式保存到结构化文件。
  • 使用 NSCoding 归档。
  • 保存到本地 SQL 数据库,如 SQLite 。
  • 使用 Core Data 。

在保存的数据量很小的时候,可以使用 NSUserDefaults 。

保存到结构化文件,需要先将整个文件加载到内存中,然后才能对其进行解析,这是一个昂贵的操作。你可以使用 SAX 处理 XML 文件,这是一个复杂的解决方案。同样,不管你想不想让所有的对象都加载到内存中。

NSCoding 也需要读取和写入文件,会遇到和上面相同的问题。

最好使用 SQLite 或 Core Data。使用这些技术,您可以执行特定的查询以仅加载所需的对象,并避免使用暴力搜索方法来检索数据。在性能方面,SQLite 和 Core Data 非常相似。

SQLite 和 Core Data 之间的最大区别在于它们的用法。Core Data表示一个对象图模型,而 SQLite 只是一个普通的 DBMS。通常苹果建议你使用 Core Data ,但是如果你有特殊的原因想避免它,你可以使用更底层的 SQLite 。

高级技巧

加快启动时间

快速的启动应用程序非常重要,尤其当用户第一次启动时。第一印象对应用程序来说意义重大。

要使应用程序快速的启动,可以做的最大的事情是执行尽可能多的异步任务,如网络请求、数据库访问或解析数据。

另外,尽量避免臃肿的 XIBs ,因为它是运行在主线程上的。

Autorelease Pool

NSAutoreleasePool 负责释放块内的自动释放对象。通常,它是由 UIKit 自动调用的。但在某些情况下,可能需要手动创建 NSAutoreleasePools 。

如果在代码中创建了许多临时对象,则会注意到在释放这些对象之前内存使用量会增加。问题是,只有在 UIKit 销毁其自动释放池之后,才会释放该内存,这意味着该内存的保存时间要比需要的长。

可以通过在自己的@autoreleasepool块中创建这些临时对象来避免这种情况。

是否缓存图像

加载 UIImage 有两种常见的方法,第一种是使用 imageNamed , 第二种是使用 imageWithContentsOfFile 。

imageNamed 的优点是在加载时缓存图像。而 imageWithContentsOfFile 不会缓存。

如果要加载只使用一次的大图像,则无需缓存该图像。在这种情况下,imageWithContentsOfFile 可以很好地满足需求。这样,操作系统就不会浪费内存来缓存图像。

imageNamed 对于在应用程序中重用的图像来说是一个更好的选择。这样,操作系统就节省了不断从磁盘加载图像的时间。

尽量避免使用 Date Formatters

如果有很多日期需要用 NSDateFormatter 解析,需要小心处理。如前所述,尽可能重用 NSDateFormatters 是一个好建议。

如果可以控制要处理的日期的格式,请尽可能选择 Unix 时间戳。Unix 时间戳是简单的整数。


关注公众号:iOS学习社区,阅读更多技术好文