第16章 图像IO

2018-09-08  本文已影响5人  cdd48b9d36e0

加载和潜伏

绘图实际消耗的时间通常并不是影响性能的因素。图片消耗很大一部分内存,而且不太可能把需要显示的图片都保留在内存中,所以需要在应用运行的时候周期性地加载和卸载图片。

缓存

如果有很多张图片要显示,最好不要提前把所有都加载进来,而是应该当移出屏幕之后立刻销毁。通过选择性的缓存,你就可以避免来回滚动时图片重复性的加载了。
缓存其实很简单:就是存储昂贵计算后的结果(或者是从闪存或者网络加载的文件)在内存中,以便后续使用,这样访问起来很快。问题在于缓存本质上是一个权衡过程 - 为了提升性能而消耗了内存,但是由于内存是一个非常宝贵的资源,所以不能把所有东西都做缓存。
何时将何物做缓存(做多久)并不总是很明显。幸运的是,大多情况下,iOS都 为我们做好了图片的缓存。
+imageNamed: 方法
之前我们提到使用加载图片有个好处在于可以立刻解压图片而不用等到绘制的时候。但是方法有另一个非常显著的好处:它在内存中自动缓存了解压后的图片,即使你自己没有保留对它的任何引用。
对于iOS应用那些主要的图片(例如图标,按钮和背景图片),使用 [UIImage imageNamed:] 加载图片是最简单最有效的方式。在nib文件中引用的图片同样也是 这个机制,所以你很多时候都在隐式的使用它。
但是 [UIImage imageNamed:] 并不适用任何情况。它为用户界面做了优化,但 是并不是对应用程序需要显示的所有类型的图片都适用。有些时候你还是要实现自 己的缓存机制,原因如下:

自定义缓存
如果要写自己的图片缓存的话,那该如何实现呢?让我们来看看要涉及哪些方
面:

NSCache
NSCache和 NSDictionary类似。你可以通过 -setObject:forkey:和-object:forkey: 方法分别来插入,检索。和字典不同的是, 在系统低内存的时候自动丢弃存储的对象。
文件格式
图片加载性能取决于加载大图的时间和解压小图时间的权衡。很多苹果的文档都 说PNG是iOS所有图片加载的最好格式。但这是极度误导的过时信息了。
PNG图片使用的无损压缩算法可以比使用JPEG的图片做到更快地解压,但是由于闪存访问的原因,这些加载的时间并没有什么区别。

第17章 图层性能

隐式绘制

文本
CATextLayer 和 UILabel 都是直接将文本绘制在图层的寄宿图中。事实上这 两种方式用了完全不同的渲染方式:在iOS 6及之前, UILabel 用WebKit的HTML 渲染引擎来绘制文本,而 CATextLayer 用的是Core Text.后者渲染更迅速,所以 在所有需要绘制大量文本的情形下都优先使用它吧。但是这两种方法都用了软件的 方式绘制,因此他们实际上要比硬件加速合成方式要慢。
不论如何,尽可能地避免改变那些包含文本的视图的frame,因为这样做的话文 本就需要重绘。例如,如果你想在图层的角落里显示一段静态的文本,但是这个图 层经常改动,你就应该把文本放在一个子图层中。
光栅化
在第四章『视觉效果』中我们提到了 CALayer 的 shouldRasterize 属性,它 可以解决重叠透明图层的混合失灵问题。同样在第12章『速度的曲调』中,它也是 作为绘制复杂图层树结构的优化方法。
启用 shouldRasterize 属性会将图层绘制到一个屏幕之外的图像。然后这个图 像将会被缓存起来并绘制到实际图层的 contents 和子图层。如果有很多的子图层 或者有复杂的效果应用,这样做就会比重绘所有事务的所有帧划得来得多。但是光 栅化原始图像需要时间,而且还会消耗额外的内存。
当我们使用得当时,光栅化可以提供很大的性能优势(如你在第12章所见),但 是一定要避免作用在内容不断变动的图层上,否则它缓存方面的好处就会消失,而 且会让性能变的更糟。
为了检测你是否正确地使用了光栅化方式,用Instrument查看一下Color Hits Green和Misses Red项目,是否已光栅化图像被频繁地刷新(这样就说明图层并不 是光栅化的好选择,或则你无意间触发了不必要的改变导致了重绘行为)。
离屏渲染
当图层属性的混合体被指定为在未预合成之前不能直接在屏幕中绘制时,屏幕外 渲染就被唤起了。屏幕外渲染并不意味着软件绘制,但是它意味着图层必须在被显 示之前在一个屏幕外上下文中被渲染(不论CPU还是GPU)。图层的以下属性将会 触发屏幕外绘制:

屏幕外渲染和我们启用光栅化时相似,除了它并没有像光栅化图层那么消耗大,子图层并没有被影响到,而且结果也没有被缓存,所以不会有长期的内存占用。但是,如果太多图层在屏幕外渲染依然会影响到性能。
有时候我们可以把那些需要屏幕外绘制的图层开启光栅化以作为一个优化方式,前提是这些图层并不会被频繁地重绘。
有时候我们可以把那些需要屏幕外绘制的图层开启光栅化以作为一个优化方式,前提是这些图层并不会被频繁地重绘。
混合和过度绘制
在第12章有提到,GPU每一帧可以绘制的像素有一个最大限制(就是所谓的fill rate),这个情况下可以轻易地绘制整个屏幕的所有像素。但是如果由于重叠图层 的关系需要不停地重绘同一区域的话,掉帧就可能发生了。
GPU会放弃绘制那些完全被其他图层遮挡的像素,但是要计算出一个图层是否被 遮挡也是相当复杂并且会消耗处理器资源。同样,合并不同图层的透明重叠像素 (即混合)消耗的资源也是相当客观的。所以为了加速处理进程,不到必须时刻不 要使用透明图层。任何情况下,你应该这样做:

减少图层数量
初始化图层,处理图层,打包通过IPC发给渲染引擎,转化成OpenGL几何图 形,这些是一个图层的大致资源开销。事实上,一次性能够在屏幕上显示的最大图 层数量也是有限的。
确切的限制数量取决于iOS设备,图层类型,图层内容和属性等。但是总得说来 可以容纳上百或上千个,下面我们将演示即使图层本身并没有做什么也会遇到的性 能问题。
剪切
在对图层做任何优化之前,你需要确定你不是在创建一些不可见的图层,图层在以下几种情况下回事不可见的。

对象回收
处理巨大数量的相似视图或图层时还有一个技巧就是回收他们。对象回收在iOS 颇为常见; UITableView 和 UICollectionView 都有用到, MKMapView 中的动 画pin码也有用到,还有其他很多例子。
对象回收的基础原则就是你需要创建一个相似对象池。当一个对象的指定实例
(本例子中指的是图层)结束了使命,你把它添加到对象池中。每次当你需要一个实例时,你就从池中取出一个。当且仅当池中为空时再创建一个新的。
这样做的好处在于避免了不断创建和释放对象(相当消耗资源,因为涉及到内存的分配和销毁)而且也不必给相似实例重复赋值。
本例中,我们只有图层对象这一种类型,但是UIKit有时候用一个标识符字符串来 区分存储在不同对象池中的不同的可回收对象类型。

上一篇下一篇

猜你喜欢

热点阅读