美文网首页
第16章 图像IO

第16章 图像IO

作者: cdd48b9d36e0 | 来源:发表于2018-09-08 22:32 被阅读5次

    加载和潜伏

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

    缓存

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

    • [UIImage imageNamed:] 方法仅仅适用于在应用程序资源束目录下的图片, 但是大多数应用的许多图片都要从网络或者是用户的相机中获取,所以 [UIImage imageNamed:] 就没法用了。
    • [UIImage imageNamed:] 缓存用来存储应用界面的图片(按钮,背景等 等)。如果对照片这种大图也用这种缓存,那么iOS系统就很可能会移除这些 图片来节省内存。那么在切换页面时性能就会下降,因为这些图片都需要重新加载。对传送器的图片使用一个单独的缓存机制就可以把它和应用图片的生命周期解耦。
    • [UIImage imageNamed:] 缓存机制并不是公开的,所以你不能很好地控制它。例如,你没法做到检测图片是否在加载之前就做了缓存,不能够设置缓存大小,当图片没用的时候也不能把它从缓存中移除。

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

    • 选择一个合适的缓存键 - 缓存键用来做图片的唯一标识。如果实时创建图片, 通常不太好生成一个字符串来区分别的图片。在我们的图片传送带例子中就很 简单,我们可以用图片的文件名或者表格索引。
    • 提前缓存 - 如果生成和加载数据的代价很大,你可能想当第一次需要用到的时 候再去加载和缓存。提前加载的逻辑是应用内在就有的,但是在我们的例子 中,这也非常好实现,因为对于一个给定的位置和滚动方向,我们就可以精确 地判断出哪一张图片将会出现。
    • 缓存失效 - 如果图片文件发生了变化,怎样才能通知到缓存更新呢?这是个非 常困难的问题(就像菲尔 卡尔顿提到的),但是幸运的是当从程序资源加载静 态图片的时候并不需要考虑这些。对用户提供的图片来说(可能会被修改或者 覆盖),一个比较好的方式就是当图片缓存的时候打上一个时间戳以便当文件 更新的时候作比较。
    • 缓存回收 - 当内存不够的时候,如何判断哪些缓存需要清空呢?这就需要到你 写一个合适的算法了。幸运的是,对缓存回收的问题,苹果提供了一个叫做 NSCache 通用的解决方案。

    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)。图层的以下属性将会 触发屏幕外绘制:

    • 圆角(当和 maskToBounds 一起使用时)
    • 图层蒙板
    • 阴影

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

    • 给视图的 属性设置一个固定的,不透明的颜色
    • 设置 opaque 属性为YES
      如果用到了图像,尽量避免透明除非非常必要。如果图像要显示在一个固定的背景颜色或是固定的背景图之前,你没必要相对前景移动,你只需要预填充背景图片就可以避免运行时混色了。
      如果是文本的话,一个白色背景的 UILabel (或者其他颜色)会比透明背景要 更高效。
      最后,明智地使用 shouldRasterize 属性,可以将一个固定的图层体系折叠成 单张图片,这样就不需要每一帧重新合成了,也就不会有因为子图层之间的混合和 过度绘制的性能问题了。

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

    • 图层在屏幕边界之外,或是在父图层边界之外。
    • 完全在一个不透明图层之后。
    • 完全透明

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

    相关文章

      网友评论

          本文标题:第16章 图像IO

          本文链接:https://www.haomeiwen.com/subject/tmolgftx.html