BBWebImage 设计思路

BBWebImage 设计思路

BBWebImage 是高性能 Swift 图片组件,用于图片下载、缓存、编解码、编辑与展现。html

GitHub 地址: https://github.com/Silence-GitHub/BBWebImagegit

效果图

下载、展现并缓存原图github

下载、渐进式解码、编辑图片,缓存编辑后的图片至内存 (Memory)、缓存原图数据至磁盘 (Disk)web

  • 添加滤镜

  • 绘制圆角、边框

性能对比

测试的图片组件有 BBWebImage (1.1.0)、SDWebImage (4.4.6 以及 FLAnimatedImage 1.0.12 用于测试 GIF)、YYWebImage (1.0.5) 和 Kingfisher (4.10.1)。测试设备是 iPhone 7,iOS 12.1。算法

  • BBWebImage 的内存缓存与磁盘缓存速度很快,针对缩略图的优点明显

  • 加载展现 GIF,BBWebImage 占用不多的 CPU 和内存

为何写这个图片组件

写 BBWebImage 最开始的目的是要解决现有图片组件中图片编辑与动图的问题。数据库

作过的项目中,图片组件都主要用 SDWebImage,显示 WebP、APNG 等格式动图用 YYWebImage。这些图片组件都很是优秀,能知足大多数使用场景的需求。YYWebImage 支持的图片格式不少,可是功能和可自定义程度不如 SDWebImage (例如自定义图片解码器)。当 BBWebImage 初版 0.1.0 发布时,SDWebImage 的最新正式版是 4.4.3,尚未图片编辑模块。有些时候须要展现编辑后的图片,例如添加滤镜、绘制圆角和边框 (防止 CALayer 设置圆角形成顿卡) 等,也须要缓存编辑后的图片。若是用 SDWebImage 下载图片并编辑,会有如下问题:缓存

  1. 若是只缓存编辑后的图片,则展现原图须要再次下载。
  2. 假设原图数据缓存至磁盘。若是不缓存编辑后的图片,须要在每次展现前重复编辑原图这个步骤。若是把编辑后的图片缓存至内存和磁盘,为了与原图区分,须要维护 cache key (在缓存中一个 key 对应原图,另外一个 key 对应编辑后的图片)。若是把编辑后的图片只缓存至内存,则为了区分从缓存中取出的图片是否通过编辑,须要判断是从内存仍是磁盘取到的图片。
  3. 若是用 Core Graphics 框架编辑图片,SDWebImage 的图片解压缩 (Decompress) 是没必要要的。编辑和解压缩步骤相似:建立 CGContext、绘制图片、建立新图片。须要在编辑前禁用图片解压缩,完成后再启用。

另外,SDWebImage 的图片降采样 (Downsample) 用了统一处理的方式,图片分辨率大于固定阈值是降采样的必要条件。问题就在于阈值是固定的,遇到多张大图的状况,这个阈值仍是太大,致使内存占用过多而崩溃。若是图片组件中有图片编辑模块,能够把图片降采样放入编辑模块,就能够自定义降采样参数,从而解决内存占用过多问题。网络

关于动图,SDWebImage 用 FLAnimatedImage 来展现 GIF,可是有性能问题。缘由是 FLAnimatedImage 没有继承 UIImage,SDWebImage 的解码器没法直接返回 FLAnimatedImage,只好在主线程中用图片数据建立 FLAnimatedImage,这一步阻塞主线程致使顿卡。具体代码分析和解决方案参见 SDWebImage 加载显示 GIF 与性能问题。解决方案能用,可是从设计的角度看,SDWebImage 使用 FLAnimatedImage 并不合适。FLAnimatedImage 只适用于 GIF,没法经过自定义解码器来支持其余格式的动图。理想的状况是,图片组件搭建好展现动图的框架,有经常使用动图的解码,能够自定义解码器来支持其余格式的动图。闭包

架构设计

主要结构

BBWebImage 的主要结构能够看下面这幅图。BBImageCache 管理图片缓存,BBImageDownloader 管理图片下载,BBImageCoderManager 管理图片编解码,BBWebImageEditor 提供图片编辑方法。BBWebImageManager 调用前四者的方法实现相应功能,对外提供一个方法实现图片加载 (缓存读取与下载)、解码、编辑和缓存。UIImageView 的扩展方法调用 BBWebImageManager 的方法获取图片用于展现。动图封装成 BBAnimatedImage,用 BBAnimatedImageView 展现。架构

BBImageCache

BBImageCache 是图片缓存协议,定义向缓存存取图片的行为。BBLRUImageCache 是默认使用的缓存,遵循 BBImageCache 协议。BBLRUImageCache 里面有内存缓存与磁盘缓存,都采用 LRU 算法 (Least recently used)。这部分设计基本参照 YYCache。内存缓存用字典和双向链表实现 LRU 算法。磁盘缓存用 SQLite 数据库存储数据相关信息 (key、大小、更新时间等),二进制数据自己根据文件大小来决定存储至 SQLite 数据库或者直接写入沙盒目录。

往内存缓存中保存的是 UIImage,取出的也是 UIImage。往磁盘缓存中存储的是 Data 或者是 UIImage,后者会被编码成 Data;取出的只是 Data,这里不会进行解码 (BBWebImageManager 拿到数据,才会用 BBImageCoderManager 进行解码)。

若是默认缓存没法知足需求,能够自定义缓存,遵循 BBImageCache 协议,替换默认缓存。

BBImageDownloader

BBImageDownloader 是图片下载协议,定义图片下载行为。BBMergeRequestImageDownloader 是默认使用的下载器,遵循 BBImageDownloader 协议。BBMergeRequestImageDownloader 会合并对同一 URL 的网络请求,防止对同一 URL 发出重复请求。每个下载任务封装成 BBImageDownloadTask (是个协议,默认实现是 BBImageDefaultDownloadTask,可自定义实现) ,包含此次下载任务的完成回调等信息。每个 URL 网络请求 (如下称为 "下载操做") 封装成 BBImageDownloadOperation (也是协议,默认实现是 BBMergeRequestImageDownloadOperation,可自定义实现),包含至少一个下载任务。

下载操做的执行顺序是,通常操做 (下载图片后要当即使用) 优先于预加载操做 (图片不须要在下载后当即使用,只是下载存入缓存),同时先进先出,也就是老的操做优先执行。虽然 SDWebImage 提供了后进先出和设置优先级的功能,但在作过的项目中并无用到。所以这里没有设计这些功能,之后须要的话能够加上。实现方法原来是用自带的 Operation 和 OperationQueue 实现,但后来想把这一部分也自定义,因而用字典和双向链表实现。一共有两组字典和双向链表的组合,一组表明通常操做队列,另外一组表明预加载操做队列。最多同时执行操做数为 6 个。操做数少于 6,有新操做进来就执行;大于等于 6,把新操做插入相应队列尾部。一个操做结束后,先从通常操做队列头部取通常操做来执行,没有的话再从预加载操做队列头部取预加载操做来执行。预加载操做还能够升级为通常操做。若是前面有预加载任务,而且相应的预加载操做进入预加载操做队列等待,后来有通常下载任务是相同的 URL,则以前的预加载操做会被移出预加载操做队列,进入通常操做操做队列而升级为通常操做,把后来的通常下载任务合并进来。

若是须要自定义图片下载行为,例如 MD5 校验等,能够考虑自定义下载任务 (遵循 BBImageDownlaodTask 协议) 或下载操做 (遵循 BBImageDownloadOperation 协议),甚至自定义整个下载器 (遵循 BBImageDownloader 协议)。

BBImageCoderManager

BBImageCoder 是图片编解码协议,定义图片编解码行为。BBImageCoderManager 遵循 BBImageCoder 协议,包含至少一个编解码器 (也遵循 BBImageCoder 协议)。用 BBImageCoderManager 来编解码时,BBImageCoderManager 会遍历其中的编解码器,尝试找到一个能完成操做的编解码器。这个图片组件中的全部图片编解码操做 (包括静图、渐进式解码、动图) 都由遵循 BBImageCoder 协议的编解码器完成,能够经过自定义编解码器来自定义编解码行为,支持不一样格式的图片。

BBWebImageEditor

BBWebImageEditor 是个结构体,包含一个字符串 key 和一个闭包 edit。闭包 edit 输入一个 UIImage, 输出一个 UIImage,用于编辑图片。字符串 key 做为图片编辑方法的惟一标识符,将与 edit 输出的 UIImage 动态关联 (经过扩展属性 bb_imageEditKey 来访问,如下称为 “edit key”)。例如,定义一个添加滤镜的图片编辑器,edit 是添加滤镜闭包,key 是 "filter",编辑后的图片的 edit key 是 "filter";定义一个绘制圆角的图片编辑器,edit 是绘制圆角闭包,key 是 "roundedCorner",编辑后的图片的 edit key 是 "roundedCorner"。原图的 edit key 为 nil。经过图片的 edit key 就能够知道图片是原图仍是某个编辑器编辑后的图片。

BBWebImageManager

BBWebImageManager 对外提供加载图片的方法 loadImage(with:)。与 SDWebImage 相似,先在内存缓存中找图片,没有的话找磁盘缓存,若是没有就下载并缓存图片。不一样的是,图片解压缩在这一层才执行 (SDWebImage 在 cache 和 download operation 中都有执行),并且这里还有图片编辑步骤。loadImage(with:) 方法的 editor 参数是 BBwebImageEditor? 类型,传 nil 表示须要原图,传某个编辑器表示须要用原图进行编辑。若是从内存缓存中取到图片,须要经过 edit key 判断图片的编辑状态 (原图、或者被编辑),决定后续步骤 (直接使用图片,直接编辑图片,须要从磁盘缓存或网络获取图片)。若是传入了编辑器做为方法参数,则不进行图片解压缩,解压缩由编辑器负责。原图数据保存至磁盘缓存,原图或编辑后的图片保存至内存缓存,经过图片的 edit key 来区分编辑状态。

这个图片组件内置的图片编辑器中有一个比较经常使用,经过 bb_imageEditorCommon(with:) 方法建立,传入的参数有 imageView 的大小和 contentMode、指望最大分辨率、圆角位置和圆角半径、边框宽度和颜色、背景色。编辑器裁剪图片,只保留 imageView 显示的部分;根据 imageView 的大小与指望最大分辨率计算降采样分辨率阈值,若是原图分辨率大于阈值就会进行降采样;绘制圆角、边框、背景色。能够用这个图片编辑器自定义降采样分辨率阈值,防止内存占用过多;绘制好圆角和边框,防止 CALayer 设置圆角和边框形成顿卡。

UIImageView 扩展

BBWebCache 是图片加载协议,定义图片加载行为。默认实现了图片加载方法 bb_setImage(with:) (如下称为 "协议加载方法"),用 BBWebImageManager 的单例加载图片,动态关联 BBWebCacheOperation 对象用于访问图片加载任务 (方便之后取消任务)。UIImageView 遵循 BBWebCache 协议,加载 image 和 highlightedImage 的扩展方法,都直接调用协议加载方法,只是传入的参数有所不一样。与此相似,UIButton、CALayer、MKAnnotationView 都有相应的扩展方法用于加载图片,也是直接调用协议加载方法。若是有自定义的 view 甚至 object 须要加载图片,也能够遵循 BBWebCache 协议,调用协议加载方法来实现加载图片的扩展方法。

动图

动图封装成 BBAnimatedImage,继承 UIImage。初始化方法除了图片数据还有动图解码器,若是没有指定解码器,则从 BBWebImageManager 单例的 BBImageCoderManager 的解码器中寻找,有合适的解码器才能初始化动图。动图向解码器获取每一帧图片以及动画时间等信息,并管理图片帧的缓存。根据总内存容量、可用内存容量来动态计算最大缓存容量,以此来清除暂时不用的图片帧同时保存将要展现的图片帧。也支持自定义最大缓存容量。动图有 bb_editor 属性,是 BBWebImageEditor? 类型。用某个图片编辑器给这个属性赋值,则会对图片帧进行编辑。这个属性默认为空,表示使用原始图片帧。

BBAnimatedImageView 继承 UIImageView,用来展现动图。用 CADisplayLink 播放动画。屏幕刷新时,向动图获取当前要展现的图片帧。这里只从缓存的图片帧中获取,避免解码阻塞主线程。同时,告诉动图下一帧要展现的图片是第几帧,由动图进行后台解码。动图会在 App 进入后台时、从 imageView 上移除时、以及收到内存警告时,清除缓存的图片帧。

BBAnimatedImageView 除了展现动图,也能够展现静图。它自己就继承 UIImageView,能够看成普通的 UIImageView 来用。BBAnimatedImage 自己继承 UIImage,与编解码协议 BBImageCoder 相符,能够在解码器中解码出来,这一点与普通的静图相同,不像 SDWebImage + FLAnimatedImage 那样静图与 GIF 不相符 (致使要对 GIF 特殊处理)。在这个框架基础上,经过自定义图片编解码器就能够支持其余格式的动图 (固然也能够支持其余格式的静图,只是这部分在讲动图)。

总结

BBWebImage 的图片缓存、下载、编解码、编辑功能均可以自定义。把动图封装成继承 UIImage 的类,用继承 UIImageView 的类进行展现,支持编辑动图的图片帧。能够自定义编解码器支持其余格式的图片。如今 BBWebImage 搭建了框架,以后会逐步完善细节。若是有编辑静图或动图的需求,或者其余相关需求,能够尝试 BBWebImage。源码及使用方法见 GitHub: https://github.com/Silence-GitHub/BBWebImage

转载请注明出处:http://www.javashuo.com/article/p-tvvqladb-hk.html

相关文章
相关标签/搜索