一、背景
日常开发过程中,图片的下载会占用大量的带宽,图片的加载会消耗大量的性能和内存,正确的使用图片显得尤为重要。
同样也经常需要在各类型控件上读取网络图片和处理本地图片,例如:UIImageView、UIBtton、NSImageView、NSButton等等。
这时候有个从网络下载和缓存图像库就会便利太多太多,很多人这时候会说,对于这块也有很多比较优秀的开源库,比如 Kingfisher、 YYWebImage、SDWebImage等等。
0 0. 框架由来,
- 本来之前呢只是想实现一个如何播放GIF,于是乎就出现第一版对任意控件实现播放GIF功能,这边只需要支持 AsAnimatable 即可快速达到支持播放GIF功能;
- 后面Boss居然又说需要对GIF图支持注入滤镜功能,于是乎又修改底层,对播放的GIF图实现滤镜功能,于是之前写的滤镜库 Harbeth 即将闪亮登场;
- 然后Boss又说,首页banner需要图像和GIF混合显示,索性就又来简单封装显示网络图像,然后根据 AssetType 来区分是属于网图还是GIF图,以达到混合显示网络图像和网络GIF以及本地图像和本地GIF混合播放功能;
- 起初也只是简单的去下载资源
Data
用于显示图像,这时候boss又要搞事情了,图像显示有点慢,于是乎又开始写网络下载模块 DataDownloader 和磁盘缓存模块 Cached ,对于已下载的图像存储于磁盘缓存方便后续再次显示,同样的网络链接地址同时下载时不会重复下载,下载完成后统一分发响应,对于下载部分资源进行断点续载功能; - 慢慢越写越发现这玩意不就是一个图像库嘛,so 它就这么的孕育而生了!!!
备注:作为参考对象,当然这里面会有一些 Kingfisher 的影子,so 再次感谢猫神!!也学到不少新东西,Thanks!
先贴地址:https://github.com/yangKJ/ImageX
待完成功能:
- 网络资源分片下载
- 控制下载最大并发量
- 低数据模式
- 图像解码优化
- 位图展示动画效果
实现方案
这边主要就是分为以下几大模块,网络下载模块、资源缓存模块、动态图播放模块、控件展示模块、解码器模块 以及 配置模块等;
这边对于资源缓存模块,已独立封装成库 Lemons 来使用,支持磁盘和内存缓存,同时也支持对待存储数据进行压缩处理从而占用更小存储空间,同时也会对磁盘数据进行时间过期和达到最大缓存空间的自动清理。
如何播放动态图像
对于这块,核心其实就是使用 CADisplayLink 不断刷新和更新动画帧图,然后对不同的控件去设置显示图像资源;
主要就是针对不同对象设置显示内容:
- UIImageView:
image
和highlightedImage
- NSImageVIew:
image
- UIButton:
image
和backgroundImage
- NSButton:
image
和alternateImage
- WKInterfaceImage:
image
对于UIView没有上述属性显示,so 这边对layer.contents
设置也是同样能达到该效果。
如何下载网络资源
对于网络图像显示,不可获取的就是对于资源的下载。
最开始的简单版,
let task = URLSession.shared.dataTask(with: url) {
(data, _, error) in
switch (data, error) {
case (.none, let error):
failed?(error)
case (let data?, _):
DispatchQueue.main.async {
self.displayImage(data: data, filters: filters, options: options)
}
let zipData = options.cacheDataZip.compressed(data: data)
let model = CacheModel(data: zipData)
storager.storeCached(model, forKey: key, options: options.cacheOption)
}
}
task.resume()
鉴于boss说的显示有点慢,能优化不。于是开始就对网络下载模块开始优化,网络数据共享和断点续下功能就孕育而生,后续再来补充分片下载功能,进一步提升网络下载速率。
网络共享
- 对于网络共享,这边其实就是采用一个单例 Networking 来设计,然后对需要下载的资源和回调响应进行存储,以链接地址md5作为key来管理查找,当数据下载回来之后,分别分发给回调响应即可,同时删除缓存的下载器和回调响应对象;
核心代码,下载过来的数据分发处理。
let downloader = DataDownloader(request: request, named: key, retry: retry, interval: interval) {
for call in cacheCallBlocks where key == call.key {
switch $0 {
case .downloading(let currentProgress):
let rest = DataResult(key: key, url: url, data: $1!, response: $2, downloadStatus: .downloading)
call.block.progress?(currentProgress)
call.block.download(.