ios图像和图形最佳实践(一)
UIKit - Images and Graphics Best Praticies - 如何在app中高效使用图形内容的技术及策略
解决问题 - 如何将先进的cpu和gpu技术集成到你的app中
当app中使用更多的cpu时,将会对电池寿命和app的响应能力产生负面的影响
可能不那么明显的是,当你的app和系统上的其他app,消耗更多内存,也会导致更高的cpu使用率,这对电池寿命和性能有进一步的不利影响
因此,我们将重点关注如何改进对这些资源的使用
对于这个问题的讨论还有什么比一个需要处理大量图像内容的app为背景更适合的呢
比如Photos app
UIImages UIKits都是用于处理图形数据的高级类
我们倾向于将app中的图形内容分为两类 - 富内容 [Content](如照片), 也可以作为UIKit中的数据类型 [Iconography 用来表示某些事物,例如显示在按钮中的图标]
image.pngimage.png
UIImageView 是UIKit提供的用于显示 UIImage的类
若采用经典的MVC模型进行类比,UIImage可以被看作模型对象
UIImageView是一个视图,这些对象和视图肩负着在经典模型中的责任
UIImage负责加载图片内容, UIimageView负责显示和渲染它
image.png image.pngImage Rending Pipline
image.png渲染是一个连续性过程,而不是一次性事件
其实有一个隐藏的阶段, 对衡量app的性能至关重要,这个阶段被称为解码
为了讨论解码,需要先讨论一个叫做"缓冲区"的概念,
Custom region of memory - Often viewed as sequence of elements
image.png缓冲区只是一段连续的内存区域,但我们倾向于使用术语 “缓存”,来表示一系列元素组成的内存,这些元素具有相同的尺寸,并通常具有相同的内部结构,而我们关注的重点是其中一种非中重要的缓存 (即图像缓存)
image.png我们用这个术语来表示一种特定缓存,它保存了某些图像在内存中的表示,此缓存的每个元素描述了图像中每个像素的颜色和透明度
因此这个缓存在内存中的大小与它包含的图像大小成正比
缓存的一个特别重要的例子是帧缓存
The frame buffer
image.png帧缓存负责在你的app中保存实际渲染后的输出,因此当你的app更新其视图层次结构时,UIKit将重新渲染app的窗口及其所有子视图 到帧缓存中
image.png该帧缓存提供每个像素的颜色信息,显示硬件将读取这些信息,以便点亮显示器上对应的像素
image.png最后一部分以固定的时间间隔发生
image.png它可能以每秒60帧的频率发生,即每1/60秒发生一次
或在配备ProMotion Display的iPad上,它的速度可以达到1/120秒发生一次
image.png如果你的app中没有任何改变,则显示硬件会将它上次看到的相同的数据从帧缓存中取出
image.png但是当你改变视图的内容,比如你为图像视图指定了一个新的UIImage,UIKit将重新渲染你的app窗口
image.png并将其放入帧缓存
image.png image.png下一次显示硬件从帧缓存中取出时,它将会得到你的新内容
image.png image.png现在你可以将图像缓存与另一种“数据缓存”进行对比
image.png这只是一种包含一系列字节的缓存,在我们的例子中,我们关心的是包含图像文件的数据缓存,也许我们已经从网络上下载了,或者从磁盘中加载了它们(数据缓存)
包含图像文件的数据缓存通常以某些元数据开头,这些元数据描述了存储在数据缓存中的图像大小
然后包含图像数据本身,图像数据以某种形式编码 如JPEG压缩或PNG
这意味着该元数据后面的字节实际上并不直接描述图像中像素的任何内容,
因此,我们可以深入了解下我们设置的这条管道
这里有一个ImageView,并且我们已经突出显示了帧缓存的区域,这块区域将由图像视图进行渲染填充
image.png我们已经为这个图像视图分配了一个UIImage,它有一个表示图像文件内容的数据缓存,其可能是从网络下载或从磁盘读取的,但我们需要用每个像素的数据来填充帧缓存
为了做到这一点,UIImage将分配一个图像缓存
Pipline in Action
image.png
其大小等于包含在数据缓存中的图像的大小,同时执行称为解码的操作,这将JPEG或PNG或其他编码的图像数据转换为每个像素的图像信息
image.png然后取决于我们的图像视图的内容模式
当UIKit要求图像视图进行渲染时,
image.png它会在将数据复制到帧缓存的过程中,对来自图像缓存的数据进行复制和缩放
现在 解码阶段是CPU密集型的,特别是对于大型图像
因此 不是每次UIKit要求图像视图渲染时都执行一次这个过程
UIImage绑定在图像缓存上,所以它只执行一次这个过程
image.png因此 你的app对于每个被解码的图像都可能会持续存在大量的内存分配
正如前面提到的那样,这种内存分配与输入图像的大小成正比,而与帧缓存实际渲染的视图的大小没有必然联系,而这会对性能产生一些相当不利的后果
image.pngapp地址空间的中的大块内存分配,可能会迫使其他相关内容远离它想要引用的内容 这种情况被称为碎片(滥用内存的后果)
如果你的app开始占用越来越多的内存 操作系统就会介入 并开始透明地压缩物理内存的内容
除了你自己的app对CPU的使用之外,CPU也需要参与这个操作,你可能会增加你无法控制的全局CPU使用率
最终你的app可能会消耗过多的物理内存 以至于操作系统需要启动终止进程 它将从低优先级的后台进程开始
如果你的app消耗了达到特定数量的内存,你的app本身可能会被终止 而其中被终止的后台进程 可能正代表用户执行某些重要工作 因此他们可能一终止就立即重新启动
所以 即使你的app可能只会在短时间内消耗内存,它也可能对CPU使用率产生深远的影响 因此我们希望减少app的内存使用量
我们可以用一种称为 下采样 的技术来实现这一目标
现在 我们来看一下 图像渲染管道的更多细节 包括我们要在其中显示图像的图像视图,实际上比要显示的图像小这一事实
image.png通常Core Animation框架在渲染阶段将负责缩小该图像 但我们可以通过使用这种下采样技术来节省一些内存
本质上 我们要做的就是捕捉该缩小的操作
我们最终会减少内存使用量 因为我们将有一个较小的解码图像缓存
这样我们设置了一个图像源,创建了一个缩略图 然后将解码图像缓存捕获到UIImage中 同时将该UIImage分配给我们的图像视图
我们就可以丢弃包含我们原有图片的数据缓存
image.png其结果是我们的app将具有一个更小的长期内存占用足迹
执行该操作的代码有几个步骤 我们先简单过一遍这个流程 初步看下效果
我们加载一张1.2M 3840x2400 的图片
image.png不使用策略加载一张图片 消耗112M内存
image.png使用downsample函数 消耗66.7M内存, 将近节省一半内存
image.pngimage.png
起作用了 ......