热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SDWebImage的缓存策略

收录:原文地址 SDWebImage 相信对大多数开发者来说,都是一个不陌生的名字。它除了帮助我们读取网络图片,还会处理这些图片的缓存。它的缓存机制到底是什么样的呢,让我给跟大家唠叨唠叨,希望你能

收录:原文地址

SDWebImage 相信对大多数开发者来说,都是一个不陌生的名字。它除了帮助我们读取网络图片,还会处理这些图片的缓存。它的缓存机制到底是什么样的呢,让我给跟大家唠叨唠叨,希望你能有收获。

基本结构

闲言少叙,咱们这就开始。 首先咱们来看看 SDWebImage 的整体结构:

SDWebImage的缓存策略
image

有一个专门的 Cache 分类用来处理图片的缓存。 这里面也有两个类 SDImageCache 和 SDImageCacheConfig。 大部分的缓存处理都在 SDImageCache 这个类中实现。其他几个文件夹咱们分别有个字的功能,因为咱们这次专门讨论缓存策略,所以其他内容暂时略过。

Memory 和 Disk 双缓存

首先,SDWebImage 的图片缓存采用的是 Memory 和 Disk 双重 Cache 机制, 听起来挺高大上吧。其实也不复杂。

我们先来看看 Memory Cache,贴一段 SDImageCache 的代码:

@interface SDImageCache ()

#pragma mark - Properties

@property (strong, nonatomic, nonnull) NSCache *memCache;

这里我们发现, 有一个叫做 memCache 的属性,它是一个 NSCache 对象,用于实现我们对图片的 Memory Cache。 SDWebImage 还专门实现了一个叫做 AutoPurgeCache 的类, 相比于普通的 NSCache, 它提供了一个在内存紧张时候释放缓存的能力:

@interface AutoPurgeCache : NSCache

@end

@implementation AutoPurgeCache

- (nonnull instancetype)init {

self = [super init];

if (self) {

#if SD_UIKIT

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];

#endif

}

return self;

}

其实就是接受系统的内存警告通知,然后清除掉自身的图片缓存。 这里大家比较少见的一个类应该是 NSCache 了。 简单来说,它是一个类似于 NSDictionary 的集合类,用于在内存中存储我们要缓存的数据。详细信息大家可以参考官方文档:https://developer.apple.com/reference/foundation/nscache。

说完 Memory Cache, 我们再来说说 Disk Cache,也就是文件缓存。

SDWebImage 会将图片存放到 NSCachesDirectory 目录中:

- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);

return [paths[0] stringByAppendingPathComponent:fullNamespace];

}

然后为每一个缓存文件生成一个 md5 文件名, 存放到文件中。

整体机制

为了节约篇幅,提升大家的阅读体验,这里尽量少贴大段代码。 我们前面介绍了 SDWebImage 同时使用内存和硬盘两种缓存。 那么我们来看看当使用 SDWebImage 读取图片时候的完整流程。 我们一般会使用 SDWebImage 对 UIKit 的扩展,直接加载图片:

[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://swiftcafe.io/images/qrcode.jpg"]];

首先这个 Category 方法 sd_setImageWithURL 内部会调用 SDWebImageManager 的 downloadImageWithURL 方法来处理这个图片 URL:

id  operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {

}];

SDWebImageManager 内部的 downloadImageWithURL 方法会先使用我们前面提到的 SDImageCache 类的 queryDiskCacheForKey 方法,查询图片缓存:

operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {

}];

再来看 queryDiskCacheForKey 方法内部, 先会查询 Memory Cache :

UIImage *image = [self imageFromMemoryCacheForKey:key];

if (image) {

doneBlock(image, SDImageCacheTypeMemory);

return nil;

}

如果 Memory Cache 查找不到, 就会查询 Disk Cache:

dispatch_async(self.ioQueue, ^{

if (operation.isCancelled) {

return;

}

@autoreleasepool {

UIImage *diskImage = [self diskImageForKey:key];

if (diskImage && self.shouldCacheImagesInMemory) {

NSUInteger cost = SDCacheCostForImage(diskImage);

[self.memCache setObject:diskImage forKey:key cost:cost];

}

dispatch_async(dispatch_get_main_queue(), ^{

doneBlock(diskImage, SDImageCacheTypeDisk);

});

}

});

查询 Disk Cache 的时候有一个小插曲,就是如果 Disk Cache 查询成功,还会把得到的图片再次设置到 Memory Cache 中。 这样做可以最大化那些高频率展现图片的效率。

如果缓存查询成功, 那么就会直接返回缓存数据。 如果不成功,接下来就开始请求网络:

id  subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {

}

请求网络使用的是 imageDownloader 属性,这个示例专门负责下载图片数据。 如果下载失败, 会把失败的图片地址写入 failedURLs 集合:

if ( error.code != NSURLErrorNotConnectedToInternet

&& error.code != NSURLErrorCancelled

&& error.code != NSURLErrorTimedOut

&& error.code != NSURLErrorInternationalRoamingOff

&& error.code != NSURLErrorDataNotAllowed

&& error.code != NSURLErrorCannotFindHost

&& error.code != NSURLErrorCannotConnectToHost) {

@synchronized (self.failedURLs) {

[self.failedURLs addObject:url];

}

}

为什么要有这个 failedURLs 呢, 因为 SDWebImage 默认会有一个对上次加载失败的图片拒绝再次加载的机制。 也就是说,一张图片在本次会话加载失败了,如果再次加载就会直接拒绝。SDWebImage 这样做可能是为了提高性能吧。这个机制可能会容易被大家忽略,所以这里特意提一下,说不定哪天遇到一些奇怪问题时候,这个知识点会帮你快速定位问题~

如果下载图片成功了,接下来就会使用 [self.imageCache storeImage] 方法将它写入缓存,并且调用 completedBlock 告诉前端显示图片:

if (downloadedImage && finished) {

[self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];

}

dispatch_main_sync_safe(^{

if (strongOperation && !strongOperation.isCancelled) {

completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);

}

});

好了,到此为止 SDWebImage 的整体图片加载流程就都走完了。 由于要控制篇幅,我这里只挑了最重点的几个节点写出来,SDWebImage 的完整机制肯定会更全面,先帮大家疏通思路。

是否要重试失败的 URL

SDWebImage 的整体图片处理流程咱们体验了一遍。 那么有哪些重要的点对咱们使用它会有帮助呢? 我帮大家整理了一些。

你可以在加载图片的时候设置 SDWebImageRetryFailed 标记,这样 SDWebImage 就会加载之前失败过的图片了。 记得我们前面提到的 failedURLs 属性了吧,这个属性是在内存中存储的,如果图片加载失败, SDWebImage 会在本次 APP 会话中都不再重试这张图片了。当然这个加载失败是有条件的,如果是超时失败,不记在内。

总之,如果你更需要图片的可用性,而不是这一点点的性能优化,那么你就可以带上 SDWebImageRetryFailed 标记:

[_image sd_setImageWithURL:[NSURL URLWithString:@"http://swiftcafe.io/images/qrcodexx.jpg"] placeholderImage:nil options:SDWebImageRetryFailed];

Disk 缓存清理策略

SDWebImage 会在每次 APP 结束的时候执行清理任务。 清理缓存的规则分两步进行。 第一步先清除掉过期的缓存文件。 如果清除掉过期的缓存之后,空间还不够。 那么就继续按文件时间从早到晚排序,先清除最早的缓存文件,直到剩余空间达到要求。

具体点,SDWebImage 是怎么控制哪些缓存过期,以及剩余空间多少才够呢? 通过两个属性:

@interface SDImageCache : NSObject

@property (assign, nonatomic) NSInteger maxCacheAge;

@property (assign, nonatomic) NSUInteger maxCacheSize;

maxCacheAge 是文件缓存的时长, SDWebImage 会注册两个通知:

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(cleanDisk)

name:UIApplicationWillTerminateNotification

object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(backgroundCleanDisk)

name:UIApplicationDidEnterBackgroundNotification

object:nil];

分别在应用进入后台和结束的时候,遍历所有的缓存文件,如果缓存文件超过 maxCacheAge 中指定的时长,就会被删除掉。

同样的, maxCacheSize 控制 SDImageCache 所允许的最大缓存空间。 如果清理完过期文件后缓存空间依然没达到 maxCacheSize 的要求, 那么就会继续清理旧文件,直到缓存空间达到要求为止。

了解了这个机制对我们有什么帮助呢? 我们来继续讲解,我们平时在使用 SDWebImage 的时候是没接触过它们的。那么以此推理,它们一定有默认值,也确实有:

static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week

上面是 maxCacheAge 的默认值,注释上写的很清楚,缓存一周。 再来看看 maxCacheSize。 翻了一遍 SDWebImage 的代码,并没有对 maxCacheSize 设置默认值。 这就意味着 SDWebImage 在默认情况下不会对缓存空间设限制。

这一点可以在 SDWebImage 清理缓存的代码中求证:

if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {

//清理缓存代码

}

说明一下, 上面代码中的 currentCacheSize 变量代表当前图片缓存占用的空间。 从这里可以看出, 只有在 maxCacheSize 大于 0 并且当前缓存空间大于 maxCacheSize 的时候才进行第二步的缓存清理。

这意味着什么呢? 其实就是 SDWebImage 在默认情况下是不对我们的缓存大小设限制的,理论上,APP 中的图片缓存可以占满整个设备。

SDWebImage 的这个特性还是比较容易被大家忽略的,如果你开发的类似信息流的 APP,应该会加载大量的图片,如果这时候按照默认机制,缓存尺寸是没有限制的,并且默认的缓存周期是一周。 就很容易造成应用存储空间占用偏大的问题。

那么可能有人会说了,现在 iPhone 的存储空间都很大,多缓存一点也不是问题吧? 但你是否知道 iOS 上还有一个用量查询的功能呢。在设置项中用户可以查看每个 APP 的空间使用情况, 如果你的 APP 占用空间比较大的话,就很容易成为用户的卸载目标,这应该是需要关注的一个细节。

另外,过多的占用缓存空间其实并不一定有用。大部分情况是一些图片被缓存下来后,很少再被重复展现。所以合理的规划缓存空间尺寸还是很有必要的。可以这样设置:

[SDImageCache sharedImageCache].maxCacheSize = 1024 * 1024 * 50; // 50M

maxCacheSize 是以字节来表示的,我们上面的计算代表 50M 的最大缓存空间。 把这行代码写在你的 APP 启动的时候,这样 SDWebImage 在清理缓存的时候,就会清理多余的缓存文件了。

结语

这次跟大家聊了聊 SDWebImage 整体流程,以及它的缓存策略。SDWebImage 是一个历时很久的开源库,并且它不断的保持着更新。 虽然是一个并不算很复杂的开源库,但仔细研读一下它的代码, 会发现它的内部很多机制设计的还是很巧妙的。 为了保证大家的阅读体验,尽量控制文章的篇幅,这里尽量选出对大家最有帮助的内容和大家分享。这篇文章结构整理花了几天时间,仔细筛选最重要的内容。想达到的效果就是,让他读起来不累,但却能很快抓住重点,让大家得到有用的信息。也希望大家对阅读体验能够提出反馈,帮助我给大家创造更好的内容。

如果想了解 SDWebImage 更详细的内容,那么它的 Github 主页就是最好的地方了,在这里也贴出来,供大家参考: https://github.com/rs/SDWebImage。

作为一个开发者,有一个学习的氛围和一个交流圈子特别重要,这是我的交流群,点击进群(备注iOS),大家有兴趣可以进群里一起交流学习!


推荐阅读
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • HDFS2.x新特性
    一、集群间数据拷贝scp实现两个远程主机之间的文件复制scp-rhello.txtroothadoop103:useratguiguhello.txt推pushscp-rr ... [详细]
  • 有没有一种方法可以在不继承UIAlertController的子类或不涉及UIAlertActions的情况下 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 本文介绍了Java中Currency类的getInstance()方法,该方法用于检索给定货币代码的该货币的实例。文章详细解释了方法的语法、参数、返回值和异常,并提供了一个示例程序来说明该方法的工作原理。 ... [详细]
  • 十大经典排序算法动图演示+Python实现
    本文介绍了十大经典排序算法的原理、演示和Python实现。排序算法分为内部排序和外部排序,常见的内部排序算法有插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。文章还解释了时间复杂度和稳定性的概念,并提供了相关的名词解释。 ... [详细]
author-avatar
太2真人05
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有