注册

最快的图像加载库-FastImageCache

FastImageCache

快速图像缓存是一种在 iOS 应用程序中存储和检索图像的高效、持久且最重要的快速方式。任何良好的 iOS 应用程序的用户体验的一部分是快速、平滑的滚动,而快速图像缓存有助于使这变得更容易。

对于像Path这样的图形丰富的应用程序,性能的一个重大负担是图像加载。从磁盘加载单个图像的传统方法太慢了,尤其是在滚动时。Fast Image Cache 就是专门为解决这个问题而创建的。

快速图像缓存的作用

  • 将相似大小和样式的图像存储在一起
  • 将图像数据保存到磁盘
  • 比传统方法更快地将图像返回给用户
  • 根据使用情况自动管理缓存过期
  • 利用基于模型的方法来存储和检索图像
  • 允许在将图像存储到缓存之前按模型处理图像


事实证明,从压缩的磁盘图像数据到用户可以实际看到的渲染核心动画层的过程非常昂贵。随着要显示的图像数量的增加,这种成本很容易导致帧速率显着下降。可滚动视图进一步加剧了这种情况,因为内容可以快速变化,需要快速处理时间才能保持 60FPS 的流畅。1

考虑从磁盘加载图像并将其显示在屏幕上时发生的工作流程:

  1. +[UIImage imageWithContentsOfFile:]使用Image I/OCGImageRef从内存映射数据创建一个此时,图像还没有被解码。
  2. 返回的图像被分配给一个UIImageView.
  3. 隐式CATransaction捕获这些层树修改。
  4. 在主运行循环的下一次迭代中,Core Animation 提交隐式事务,这可能涉及创建已设置为图层内容的任何图像的副本。根据图像,复制它涉及以下部分或全部步骤:2
    1. 缓冲区被分配用于管理文件 IO 和解压操作。
    2. 文件数据从磁盘读取到内存中。
    3. 压缩的图像数据被解码为其未压缩的位图形式,这通常是一个非常占用 CPU 的操作。3
    4. 然后 Core Animation 使用未压缩的位图数据来渲染图层。

这些成本很容易累积并扼杀感知的应用程序性能。特别是在滚动时,给用户呈现的用户体验不尽如人意,与 iOS 的整体体验不符。


解决方案

快速图像缓存使用各种技术最大限度地减少(或完全避免)上述大部分工作:

映射内存

快速图像缓存工作原理的核心是图像表。图像表类似于精灵表,通常用于 2D 游戏。图像表将相同尺寸的图像打包到一个文件中。该文件只打开一次,只要应用程序保留在内存中,就保持打开状态以供读取和写入。

图像表使用mmap系统调用将文件数据直接映射到内存中。没有memcpy发生。这个系统调用只是在磁盘上的数据和内存区域之间创建一个映射。

当请求图像缓存返回特定图像时,图像表(以恒定时间)在它维护的文件中找到所需图像数据的位置。该文件数据区域被映射到内存中,并创建一个新CGImageRef的后备存储映射的文件数据。

当返回的CGImageRef(包装成 a UIImage)准备好被绘制到屏幕上时,iOS 的虚拟内存系统页面中的实际文件数据。这是使用映射内存的另一个好处;VM 系统会自动为我们处理内存管理。此外,映射内存“不计入”应用程序的实际内存使用量。

以类似的方式,当图像数据被存储在图像表中时,会创建一个内存映射位图上下文。与原始图像一起,此上下文被传递到图像表的相应实体对象。该对象负责将图像绘制到当前上下文中,可选地进一步配置上下文(例如,将上下文裁剪为圆角矩形)或进行任何额外的绘制(例如,在原始图像上绘制叠加图像)。mmap将绘制的图像数据编组到磁盘,因此不会在内存中分配图像缓冲区。

未压缩的图像数据

为了避免昂贵的图像解压缩操作,图像表将未压缩的图像数据存储在它们的文件中。如果源图像被压缩,则必须首先解压缩图像表才能使用它。这是一次性成本。此外,可以利用图像格式系列为一组相似的图像格式只执行一次这种解压缩。

然而,这种方法有明显的后果。未压缩的图像数据需要更多的磁盘空间,压缩和未压缩文件大小之间的差异可能很大,尤其是对于 JPEG 等图像格式。出于这个原因,快速图像缓存最适用于较小的图像,尽管没有强制执行此操作的 API 限制。

字节对齐

对于高性能滚动,Core Animation 能够使用图像而无需首先创建副本是至关重要的。Core Animation 创建图像副本的原因之一是图像底层CGImageRef正确对齐的每行字节值必须是 的倍数8 pixels × bytes per pixel对于典型的 ARGB 图像,对齐的每行字节值是 64 的倍数。每个图像表都配置为从一开始就为 Core Animation 始终正确地对每个图像进行字节对齐。因此,当从图像表中检索图像时,它们已经处于 Core Animation 可以直接使用的形式,而无需创建副本。

为了便于项目集成,Fast Image Cache 可作为CocoaPod 使用

手动

创建图像格式

每个图像格式对应一个图像缓存将使用的图像表。可以使用相同的源图像来渲染它们存储在图像表中的图像的图像格式应该属于相同的图像格式系列有关如何确定适当的最大计数的更多信息,请参阅图像表大小

   



配置图像缓存

一旦定义了一种或多种图像格式,就需要将它们分配给图像缓存。除了分配图像缓存的委托之外,没有其他可以在图像缓存本身上配置的内容。


实体是符合FICEntity协议的对象实体唯一标识图像表中的条目,并且它们还负责绘制它们希望存储在图像缓存中的图像。已经定义了模型对象(可能由 Core Data 管理)的应用程序通常是合适的候选实体。

 



这是该FICEntity协议的示例实现















理想情况下,实体的UUID永远不应该改变。这就是为什么在应用程序使用从 API 检索的资源的情况下,它与模型对象的服务器生成的 ID 很好地对应。

一个实体的可以改变。例如,如果用户更新了他们的个人资料照片,则该照片的 URL 也应更改。保持不变,标识相同的用户,但改变了个人资料照片网址将表明,有一个新的源图像。sourceImageUUID UUID

注意:通常,最好对用于定义UUID和 的任何标识符进行哈希处理sourceImageUUIDFast Image Cache 提供了实用功能来执行此操作。由于散列可能很昂贵,因此建议仅计算一次散列(或仅在标识符更改时)并存储在实例变量中。

当要求图像缓存为特定实体和格式名称提供图像时,该实体负责提供 URL。URL 甚至不需要指向实际资源——例如,URL 可能由自定义 URL 方案构成——但它必须是一个有效的 URL。

图像缓存仅使用这些 URL 来跟踪哪些图像请求已经在进行中;正确处理对同一图像的图像缓存的多个请求,而不会浪费任何精力。选择使用 URL 作为键控图像缓存请求的基础实际上补充了许多实际应用程序设计,其中图像资源(而不是图像本身)的 URL 包含在服务器提供的模型数据中。

注意:快速图像缓存不提供任何网络请求机制。这是图像缓存委托的责任。

最后,一旦源图像可用,实体就会被要求提供一个绘图块。将存储最终图像的图像表设置文件映射位图上下文并调用实体的绘图块。这使得每个实体可以方便地决定如何处理特定图像格式的源图像。

从图像缓存中请求图像

快速图像缓存在 Cocoa 常见的按需、延迟加载设计模式下工作。

XXUser *user = [self _currentUser];
NSString *formatName = XXImageFormatNameUserThumbnailSmall;
FICImageCacheCompletionBlock completionBlock = ^(id <FICEntity> entity, NSString *formatName, UIImage *image) {
_imageView.image = image;
[_imageView.layer addAnimation:[CATransition animation] forKey:kCATransition];
};

BOOL imageExists = [sharedImageCache retrieveImageForEntity:user withFormatName:formatName completionBlock:completionBlock];

if (imageExists == NO) {
_imageView.image = [self _userPlaceholderImage];
}

统计数据

以下统计数据是从演示应用程序的运行中测得的:

方法滚动性能磁盘使用情况RPRVT 1
传统的~35FPS568KB2.40MB1.06MB+1.34MB
快速图像缓存~59FPS2.2MB1.15MB1.06MB+0.09MB


demo下载及常见问题:https://github.com/path/FastImageCache

源码下载:FastImageCache-master.zip


0 个评论

要回复文章请先登录注册