BBWebImage 设计思路

BBWebImage 设计思路

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

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

效果图

下载、展示并缓存原图

original_image.gif

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

  • 添加滤镜

edit_filter.gif

  • 绘制圆角、边框

edit_common.gif

性能对比

测试的图片组件有 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 的内存缓存与磁盘缓存速度很快,针对缩略图的优势明显

compare_memoryCache.png

compare_diskCache.png

  • 加载展示 GIF,BBWebImage 占用很少的 CPU 和内存

compare_gif_CPU_memory.png

为什么写这个图片组件

写 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 展示。

architecture.png

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

转载请注明出处:https://www.cnblogs.com/silence-cnblogs/p/10442984.html

转载于:https://www.cnblogs.com/silence-cnblogs/p/10442984.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/484091.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

清华本科、港科大准博士被指论文抄袭,网友:这是有技巧的“洗稿”

来源:整理自新智元、Reddit、知乎等不是吧?清华自动化本科,香港科技大学硕士生发表的顶会论文竟然是抄的?而且抄袭对象还是另一篇顶会论文?近日,眼尖的网友发现两篇分别发表在 ICML 2021 和 ICCV 2021 两大…

MATLAB深度学习入门

1. 加载图像 1.1 使用imread函数加载图像,可以加载GIF、JPEG、PNG等大多数标准文件格式图像。 Import an image img imread("file.jpg")1.2 采用**imshow()**来显示图像。 imshow(img)1.3 采用alexnet函数可以创建预定义的深度网络AlexNet的副本。 de…

SpringBoot笔记整理(三)

SpringBoot笔记整理(一) SpringBoot笔记整理(二) SpringBoot笔记整理(三) SpringBoot笔记整理(四) Web开发 1、使用SpringBoot: 1)创建SpringBoot应用&…

图神经网络,如何变深?

来源:智源社区版式制作:Camel撰文:侯振宇编辑:贾 伟现实世界中很多很多任务可以描述为图(Graph)问题,比如社交网络,蛋白质结构,交通路网数据,图是一个很值得研究的领域。近年来&am…

机器学习分类与性能度量

机器学习分类与性能度量 数据集:训练集(Training Set)、验证集(Validation Set)和测试集(Test Set) 1、 当模型无需人为设定超参数,所用参数都通过学习得到,则不需要验证集。验证集适合多个不同超参数训练多个模型,通过验证集,…

中国人民大学孟小峰教授:“科学数据智能——人工智能在科学发现中的机遇与挑战”...

来源:国家自然科学基金委员会微信公号 孟小峰 博士,中国人民大学教授,博士生导师,CCF会士。主要研究方向为数据库理论与系统、大数据管理系统、大数据隐私保护、大数据融合与智能、大数据实时分析、社会计算等。摘 要随着全球各科…

Java 面向对象语言基础

面向过程 就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。 面向对象 是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解…

全球首例!这台机器人发明了两项新技术,已获专利授权!

文章来源:EETOP几天前前人工智能系统专利申请权有最新发展。南非率先成为第一个授予人工智能专利权的国家,承认人工智能机器人DABUS为「发明者」,6日澳大利亚联邦法院也做出裁决:发明者可以是非人类。 这算是有历史里程碑意义的判…

成员变量、局部变量、实例变量、静态变量、类变量、常量

成员变量: 直接在类中声明的变量叫成员变量(又称全局变量) ▲ 初始化: 如果未对成员变量设置初始值,则系统会根据成员变量的类型自动分配初始值:int分配初始值0、boolean分配初始值false,而自定义类型则分配初始值null…

行业洞见 | 一文了解自动驾驶汽车

文章来源:ancholabs.medium.com文字翻译: 新能源情报局封面配图:原作者驾驶是人类日常从事的活动中最困难的之一。遵循交通规则,通过眼神与其他司机沟通交流、对天气状况做出反应是成为优秀司机的先决条件。在自动驾驶汽车中&…

MATLAB高光谱图像处理基础

介绍高光谱图像的基本知识,便通过MATLAB对高光谱图像进行基本的处理。 文章目录前言一、高光谱图像二、MATLAB高光谱图像处理1.加载.MAT文件数据2.图像的显示3.图像维度变换总结前言 高光谱图像是一个立方体结构,维度为M x N x B,M为水平方向…

中国千亿科技巨头,成功收购英国最大芯片制造商!

来源:世界先进制造技术论坛内容来源:每日经济新闻综合自每经App、界面新闻、财联社等本期编辑:小艾 在全球缺芯的背景下,8月15日,国产芯片巨头闻泰科技(600745.SH)披露,旗下全资子公…

MATLAB图自编码器

通过MATLAB来实现图自编码器,用于高光谱图像特征的提取。 文章目录前言一、MATLAB相关知识二、编写算法1.图自编码器搭建2.可视化相关参数总结前言 算法输入数据: 图节点属性矩阵;邻接矩阵;概率p; W为自编码器的隐藏…

考夫曼:破解大脑代码并创建真正的人工智能

来源:CSDN博主「CDA数据分析师」原文链接:https://blog.csdn.net/yoggieCDA/article/details/1045567421955年,计算机科学家约翰麦卡锡(John McCarthy)首次创造了“人工智能”一词。当时,人工智能是对可以表…

MATLAB高光谱图像构建KNN图

在高光谱图像的特征提取过程中,采用非线性降维的方式对高光谱图像降维的过程中,采用图自编码器来对数据进行降维,需要将利用高光谱图像的结构信息和内容信息,则需要将高光谱图像数据构造为一个图结构,图结构的构建需要…

类脑量子叠加脉冲神经网络:从量子大脑假说到更好的人工智能

来源:神经现实作者:曾毅研究团队 |封面:Mario De Meyer排版:光影以深度神经网络为代表的现代人工智能模型在识别图像、语音、文字等模式信息任务取得优异表现。然而,生物大脑具有处理复杂多变的环境信息的能力&#xf…

遥感图像分类原理

1.原理 1.1 每一个像元作为一个样本,波段作为特征,理想情况下,同类地物应该具有相同或相似的特征描述,因此像元在n维特征空间中聚集在一起,而不同地物应该具有不同的特征描述,因此不同特征地物像元在n维空间…

2019第一次作业

10.实验代码 include <stdio.h> int main(void) { int i, f, X, N; scanf("%d",&N); int a [N]; scanf("%d",&X); f0; for(i0;i<N;i){ scanf("%d",&a[i]); if(a[i]X){ printf("%d",i); f; } } if(f0){ printf(&q…

分布式——ACID原则 CAP理论

分布式计算的原理 分布式计算就是将计算任务分摊到大量的计算节点上&#xff0c;一 起完成海量的计算任务。而分布式计算的原理和并行计算 类似&#xff0c;就是将一个复杂庞大的计算任务适当划分为一个个 小任务&#xff0c;任务并行执行&#xff0c;只不过分布式计算会将这些…

Matlab高光谱样本相关性分析

一.高光谱图像数据分析 1.分析光谱波段的相关性 1.1 对于维度为(m,n,b)的高光谱数据&#xff0c;使用reshape函数将其变换为(N,b)维度的&#xff0c;其中N m x n&#xff0c;表示像素数量&#xff0c;b表示波段数。 1.2 分析样本(像素)间波段的相关性&#xff0c;采用函数c…