该文章阅读的SDWebImage的版本为4.3.3。
这个类是SDWebImage
中负责缓存相关功能的类。
1.类型定义
/**
这个枚举定义了缓存类型
*/
typedef NS_ENUM(NSInteger, SDImageCacheType) {
/**
不缓存
*/
SDImageCacheTypeNone,
/**
用磁盘缓存
*/
SDImageCacheTypeDisk,
/**
用内存缓存
*/
SDImageCacheTypeMemory
};
/**
这个枚举定义了缓存选项
*/
typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
/**
默认情况下,如果内存中有缓存,是不会再去磁盘中查找的,设置这个选项,可以在查找内存中缓存的同时也查找磁盘中的缓存
*/
SDImageCacheQueryDataWhenInMemory = 1 << 0,
/**
默认情况下,同步查询内存缓存,异步访问磁盘缓存,设置这个选项,可以强制同步查询磁盘缓存
*/
SDImageCacheQueryDiskSync = 1 << 1
};
/**
定义了用于缓存查询结果的回调block
*/
typedef void(^SDCacheQueryCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType);
/**
定义了用于缓存是否存在的回调block
*/
typedef void(^SDWebImageCheckCacheCompletionBlock)(BOOL isInCache);
/**
定义了用于计算磁盘缓存大小的回调block
*/
typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize);
2.公共属性
/**
配置对象
*/
@property (nonatomic, nonnull, readonly) SDImageCacheConfig *config;
/**
内存中分配给缓存的最大大小
*/
@property (assign, nonatomic) NSUInteger maxMemoryCost;
/**
缓存最大对象数量
*/
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;
3.公共方法
- 初始化相关方法
/**
获取单例对象
*/
+ (nonnull instancetype)sharedImageCache;
/**
通过指定命名空间(也就是保存图像的文件夹名字)来初始化图片缓存类
*/
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns;
/**
通过指定命名空间和磁盘目录来初始化图片缓存类
*/
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory NS_DESIGNATED_INITIALIZER;
- 缓存路径相关方法
/**
根据命名空间获取缓存保存的磁盘路径
*/
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace;
/**
添加一个只读的图像缓存磁盘路径,这个方法的意义在于将磁盘中其他目录下的图像也作为SDWebImage加载图像的缓存
*/
- (void)addReadOnlyCachePath:(nonnull NSString *)path;
/**
获取指定目录下,指定密钥的缓存路径
*/
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path;
/**
获取默认目录下指定密钥的缓存路径
*/
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key;
- 存储相关方法
/**
将图像异步缓存到指定密钥下的内存和磁盘中
*/
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/**
将图像异步缓存到指定密钥下的内存中,可以选择是否缓存到磁盘中
*/
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/**
将图像异步缓存到指定密钥下的内存中,
可以选择是否缓存到磁盘中,
并且可以直接将图像的数据保存到磁盘中,
就不必再通过将原图像编码后获取图像的数据再保存到磁盘中,
以节省硬件资源
*/
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/**
将图像数据同步缓存到指定密钥下的磁盘中
*/
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;
- 查询相关方法
/**
异步查询图像是否在磁盘中
*/
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
同步查询图像是否在磁盘中
*/
- (BOOL)diskImageDataExistsWithKey:(nullable NSString *)key;
/**
异步查询图像是否在磁盘中并获取图像数据
*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
/**
以指定缓存类型来异步查询图像是否在磁盘中并获取图像数据
*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock;
/**
同步查询内存中指定密钥的图像
*/
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key;
/**
同步查询磁盘中指定密钥的图像
*/
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key;
/**
同步查询缓存(内存和磁盘)中指定密钥的图像
*/
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key;
- 删除相关方法
/**
异步删除内存和磁盘中缓存的指定密钥的图像
*/
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion;
/**
异步删除内存中缓存的指定密钥的图像,
并且可以选择是否也从磁盘中删除对应缓存
*/
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion;
- 清理缓存相关方法
/**
清除内存中所有缓存的图像
*/
- (void)clearMemory;
/**
异步清除磁盘中所有的缓存图像
*/
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion;
/**
异步清除磁盘中过期的缓存图像
*/
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;
- 缓存信息相关方法
/**
获取磁盘中缓存占用的大小
*/
- (NSUInteger)getSize;
/**
获取磁盘中缓存图像的数量
*/
- (NSUInteger)getDiskCount;
/**
异步获取磁盘中缓存占用的大小
*/
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock;
4.私有宏
/**
利用GCD的信号量实现加锁的效果
*/
#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
/**
利用GCD的信号量实现解锁的效果
*/
#define UNLOCK(lock) dispatch_semaphore_signal(lock);
5.私有函数
/**
获取图像对象内存开销
*/
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
// 如果是MAC就是图像的长宽相乘
return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
// 否则就是还要再乘以图像的分辨率
return image.size.height * image.size.width * image.scale * image.scale;
#endif
}
6.私有类SDMemoryCache
这个类是继承自NSCache
,负责管理图像的内存缓存。
6.1 类扩展
/**
图像缓存
*/
@property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache;
/**
保证缓存操作的线程安全
*/
@property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock;
6.2 实现
- (void)dealloc {
// 移除对内存警告通知的监听
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
- (instancetype)init {
self = [super init];
if (self) {
// 初始化属性
self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
self.weakCacheLock = dispatch_semaphore_create(1);
// 添加通知监听内存警告
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning:)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
return self;
}
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
// 监听到内存警告后移除掉所有的缓存
[super removeAllObjects];
}
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
// 调用父类方法赋值
[super setObject:obj forKey:key cost:g];
if (key && obj) {
// 重写设置方法,保存到弱缓存中
LOCK(self.weakCacheLock);
[self.weakCache setObject:obj forKey:key];
UNLOCK(self.weakCacheLock);
}
}
- (id)objectForKey:(id)key {
// 调用父类方法获取值
id obj = [super objectForKey:key];
if (key && !obj) {
// 如果没有值就从弱缓存中获取
LOCK(self.weakCacheLock);
obj = [self.weakCache objectForKey:key];
UNLOCK(self.weakCacheLock);
if (obj) {
// 如果获取到了就保存到缓存中
NSUInteger cost = 0;
if ([obj isKindOfClass:[UIImage class]]) {
cost = SDCacheCostForImage(obj);
}
[super setObject:obj forKey:key cost:cost];
}
}
return obj;
}
- (void)removeObjectForKey:(id)key {
// 调用父类方法移除
[super removeObjectForKey:key];
if (key) {
// 从弱缓存中移除
LOCK(self.weakCacheLock);
[self.weakCache removeObjectForKey:key];
UNLOCK(self.weakCacheLock);
}
}
- (void)removeAllObjects {
// 调用父类方法移除
[super removeAllObjects];
// 移除弱缓存
LOCK(self.weakCacheLock);
[self.weakCache removeAllObjects];
UNLOCK(self.weakCacheLock);
}
6.3 分析
这个类本身就是继承自NSCache
具有缓存功能,但是还是添加了一个NSMapTable
类型的类扩展属性weakCache
进行二次缓存。
为什么还要再通过属性再缓存一遍?关键就在于weakCache
属性它是NSMapTable
类型的,可以设置其value
为弱引用。
那这有什么好处呢?当接收到内存警告时,本类会清空缓存,虽然缓存中的图像对象都被清除了,但是部分图像对象会被UIImageView
、UIButton
等实例对象所持有,这也就意味着有部分图像对象还在内存中。
为了提高效率,可以在缓存被清除,但是图片对象还被其它实例对象所持有的情况下,直接将实例对象持有的图像对象拿来做缓存,这样就比去磁盘中去取效率更高。这就用到了weakCache
属性,因为weakCache
属性的value
为弱引用,所以只要还有实例对象持有,就可以通过weakCache
属性找到对应图像对象。
7.类扩展属性
/**
图像缓存对象
*/
@property (strong, nonatomic, nonnull) SDMemoryCache *memCache;
/**
磁盘缓存路径
*/
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
/**
自定义磁盘缓存路径
*/
@property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths;
/**
读写队列
*/
@property (strong, nonatomic, nullable) dispatch_queue_t ioQueue;
/**
文件管理对象
*/
@property (strong, nonatomic, nonnull) NSFileManager *fileManager;
8.实现
- 生命周期方法
+ (nonnull instancetype)sharedImageCache {
// 获取单例对象
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (instancetype)init {
// 以default为指定命名空间进行初始化
return [self initWithNamespace:@"default"];
}
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns {
// 根据指定的命名空间生成磁盘缓存目录
NSString *path = [self makeDiskCachePath:ns];
// 以指定的命名空间和生成的磁盘缓存目录进行初始化
return [self initWithNamespace:ns diskCacheDirectory:path];
}
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory {
if ((self = [super init])) {
// 生成命名空间的全名
NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
// 生成用于读写的串行队列
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
// 生成缓存配置对象
_config = [[SDImageCacheConfig alloc] init];
// 生成内存缓存对象
_memCache = [[SDMemoryCache alloc] init];
_memCache.name = fullNamespace;
if (directory != nil) {
// 如果有磁盘缓存目录,就拼接命名空间路径
_diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
} else {
// 如果没有磁盘缓存目录,就根据命名空间生成一个
NSString *path = [self makeDiskCachePath:ns];
_diskCachePath = path;
}
// 同步串行队列中生成文件管理者对象
dispatch_sync(_ioQueue, ^{
self.fileManager = [NSFileManager new];
});
#if SD_UIKIT
// 添加通知监听应用将要被杀死
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deleteOldFiles)
name:UIApplicationWillTerminateNotification
object:nil];
// 添加通知监听应用已经进入后台
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundDeleteOldFiles)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
#endif
}
return self;
}
- (void)dealloc {
// 移除对通知的监听
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- 缓存路径方法
- (void)addReadOnlyCachePath:(nonnull NSString *)path {
// 如果没有自定义路径数组对象就生成一个
if (!self.customPaths) {
self.customPaths = [NSMutableArray new];
}
// 如果要添加的路径不在数组中就添加进去
if (![self.customPaths containsObject:path]) {
[self.customPaths addObject:path];
}
}
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path {
// 根据密钥获取缓存文件名字
NSString *filename = [self cachedFileNameForKey:key];
// 返回缓存文件的路径
return [path stringByAppendingPathComponent:filename];
}
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key {
// 获取当前缓存目录下指定密钥的缓存文件的路径
return [self cachePathForKey:key inPath:self.diskCachePath];
}
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
// 获取传入密钥的UTF8编码
const char *str = key.UTF8String;
// 如果获取失败就设置为空
if (str == NULL) {
str = "";
}
// 进行MD5加密
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
// 获取密钥的URL对象
NSURL *keyURL = [NSURL URLWithString:key];
// 获取后缀名
NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
// 文件名就是传入密钥的MD5编码然后拼上原文件后缀名
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
return filename;
}
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
// 获取Caches目录
NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
// 创建Caches目录下的路径
return [paths[0] stringByAppendingPathComponent:fullNamespace];
}
- 保存方法
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
// 默认是缓存到磁盘中的
[self storeImage:image imageData:nil forKey:key toDisk:YES completion:completionBlock];
}
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
// 直接调用全能方法
[self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock];
}
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
// 图像对象和密钥是必传的
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// 如果需要缓存到内存
if (self.config.shouldCacheImagesInMemory) {
// 获取图像对象的内存占用量
NSUInteger cost = SDCacheCostForImage(image);
// 调用内存缓存对象缓存图像对象
[self.memCache setObject:image forKey:key cost:cost];
}
// 如果需要缓存到磁盘
if (toDisk) {
// 异步串行队列
dispatch_async(self.ioQueue, ^{
// 自动释放池
@autoreleasepool {
// 获取图像数据
NSData *data = imageData;
// 如果没有图像数据但是有图像对象
if (!data && image) {
// 如果有透明度就是PNG类型,否则就是JPEG类型
SDImageFormat format;
if (SDCGImageRefContainsAlpha(image.CGImage)) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
// 对图像对象编码获取图像数据
data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format];
}
// 将图像数据保存到磁盘中
[self _storeImageDataToDisk:data forKey:key];
}
// 如果有完成回调block就主线程异步调用一下
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
// 如果不需要缓存到磁盘就直接回调block
if (completionBlock) {
completionBlock();
}
}
}
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
// 图像对象和密钥是必传的
if (!imageData || !key) {
return;
}
// 异步串行队列
dispatch_sync(self.ioQueue, ^{
// 将图像数据保存到磁盘中
[self _storeImageDataToDisk:imageData forKey:key];
});
}
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
// 图像对象和密钥是必传的
if (!imageData || !key) {
return;
}
// 判断磁盘缓存路径是否存在,不存在就创建
if (![self.fileManager fileExistsAtPath:_diskCachePath]) {
[self.fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// 根据密钥获取要保存到的磁盘路径
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// 转换成url对象
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
// 将图像数据写入到指定路径中
[imageData writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];
// 禁用iCloud备份
if (self.config.shouldDisableiCloud) {
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
- 查询方法
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
// 异步串行队列
dispatch_async(self.ioQueue, ^{
// 根据传入的密钥判断是否存在
BOOL exists = [self _diskImageDataExistsWithKey:key];
// 主队列异步回调存在情况
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(exists);
});
}
});
}
- (BOOL)diskImageDataExistsWithKey:(nullable NSString *)key {
// 如果没有密钥就返回NO
if (!key) {
return NO;
}
// 同步串行队列
__block BOOL exists = NO;
dispatch_sync(self.ioQueue, ^{
// 根据传入的密钥判断是否存在
exists = [self _diskImageDataExistsWithKey:key];
});
// 返回存在情况
return exists;
}
- (BOOL)_diskImageDataExistsWithKey:(nullable NSString *)key {
// 如果没有密钥就返回NO
if (!key) {
return NO;
}
// 判断缓存路径是否存在
BOOL exists = [self.fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];
// 如果不存在就删除文件后缀后再查找一遍
if (!exists) {
exists = [self.fileManager fileExistsAtPath:[self defaultCachePathForKey:key].stringByDeletingPathExtension];
}
// 返回存在情况
return exists;
}
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
// 从内存缓存对象中查找指定密钥对应的图像对象是否存在
return [self.memCache objectForKey:key];
}
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
// 从磁盘缓存中查找指定密钥对应的图像对象
UIImage *diskImage = [self diskImageForKey:key];
// 如果找到了图像对象,并且设置了需要缓存到内存的选项
if (diskImage && self.config.shouldCacheImagesInMemory) {
// 将图像对象缓存到内存对象中
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
// 返回图像对象
return diskImage;
}
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key {
// 先查找内存中是否有缓存
UIImage *image = [self imageFromMemoryCacheForKey:key];
// 如果有就直接返回
if (image) {
return image;
}
// 如果内存中没有,就查找磁盘中是否有缓存
image = [self imageFromDiskCacheForKey:key];
// 返回图像对象
return image;
}
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
// 获取到密钥对应的缓存路径
NSString *defaultPath = [self defaultCachePathForKey:key];
// 获取路径下的图像数据
NSData *data = [NSData dataWithContentsOfFile:defaultPath options:self.config.diskCacheReadingOptions error:nil];
// 如果有数据就直接返回
if (data) {
return data;
}
// 如果没找到就删除文件后缀后再找一遍
data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
// 如果有数据就直接返回
if (data) {
return data;
}
// 如果在默认目录下没找到,就从用户自定义的目录中查找
NSArray<NSString *> *customPaths = [self.customPaths copy];
// 遍历自定义目录
for (NSString *path in customPaths) {
// 获取磁盘缓存路径
NSString *filePath = [self cachePathForKey:key inPath:path];
// 获取路径下的图像数据
NSData *imageData = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
// 如果有数据就直接返回
if (imageData) {
return imageData;
}
// 如果没找到就删除文件后缀后再找一遍
imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
// 如果有数据就直接返回
if (imageData) {
return imageData;
}
}
// 如果都没找到返回空
return nil;
}
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
// 获取密钥对应的图像数据
NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
// 返回密钥对应的图像对象
return [self diskImageForKey:key data:data];
}
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data {
// 如果有数据才解码
if (data) {
// 调用编解码管理器解码图像数据
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data];
// 根据密钥缩放图像对象
image = [self scaledImageForKey:key image:image];
// 如果设置了需要解压图像对象
if (self.config.shouldDecompressImages) {
// 解压图像对象
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
}
// 返回图像对象
return image;
} else {
// 如果没有图像数据就直接返回空
return nil;
}
}
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
// 将函数封装成方法
return SDScaledImageForKey(key, image);
}
- (NSOperation *)queryCacheOperationForKey:(NSString *)key done:(SDCacheQueryCompletedBlock)doneBlock {
// 调用下面的万能方法
return [self queryCacheOperationForKey:key options:0 done:doneBlock];
}
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock {
// 必须要传密钥
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// 先查看内存中是否有缓存
UIImage *image = [self imageFromMemoryCacheForKey:key];
// 是否只查找内存缓存:从内存中获取到了缓存并且没有设置需要查找磁盘缓存
BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryDataWhenInMemory));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}
// 创建操作对象
NSOperation *operation = [NSOperation new];
// 创建一个查询磁盘缓存的block
void(^queryDiskBlock)(void) = ^{
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
// 创建自动释放池
@autoreleasepool {
// 查找磁盘中的缓存
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
// 声明变量保存磁盘中的缓存图像对象
UIImage *diskImage;
// 声明变量保存缓存类型
SDImageCacheType cacheType = SDImageCacheTypeDisk;
if (image) {
// 如果从内存中已经获取到了缓存,就直接赋值
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
// 如果内存中没有缓存,但是磁盘中有缓存
// 将图像数据解码成图像对象
diskImage = [self diskImageForKey:key data:diskData];
if (diskImage && self.config.shouldCacheImagesInMemory) {
// 如果有图像对象,并且需要缓存到内存中,就缓存到内存中
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
}
if (doneBlock) {
// 如果设置了同步查询,就直接回调,否则就主队列异步回调
if (options & SDImageCacheQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
}
}
};
// 如果设置了同步查询,就直接调用查询block,否则就串行队列异步调用
if (options & SDImageCacheQueryDiskSync) {
queryDiskBlock();
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation;
}
- 移除缓存方法
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion {
// 默认磁盘中的缓存也删除
[self removeImageForKey:key fromDisk:YES withCompletion:completion];
}
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion {
// 密钥是必传的
if (key == nil) {
return;
}
// 如果设置了需要缓存到内存中的选项
if (self.config.shouldCacheImagesInMemory) {
// 删除内存缓存对象中的缓存
[self.memCache removeObjectForKey:key];
}
// 如果要删除磁盘中的缓存
if (fromDisk) {
// 串行队列异步
dispatch_async(self.ioQueue, ^{
// 删除磁盘中的缓存
[self.fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
主队列异步回调
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
} else if (completion){
// 如果不需要删除磁盘中的缓存就直接回调
completion();
}
}
- 内存缓存对象设置方法
这些方法都是对内存缓存对象属性setting和getting的二次封装
- (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost {
self.memCache.totalCostLimit = maxMemoryCost;
}
- (NSUInteger)maxMemoryCost {
return self.memCache.totalCostLimit;
}
- (NSUInteger)maxMemoryCountLimit {
return self.memCache.countLimit;
}
- (void)setMaxMemoryCountLimit:(NSUInteger)maxCountLimit {
self.memCache.countLimit = maxCountLimit;
}
- 缓存清理方法
- (void)clearMemory {
// 移除内存缓存对象中所有的缓存
[self.memCache removeAllObjects];
}
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion {
// 串行队列异步调用
dispatch_async(self.ioQueue, ^{
// 移除掉磁盘缓存目录下的所有缓存
[self.fileManager removeItemAtPath:self.diskCachePath error:nil];
// 重新创建一个用于缓存的目录
[self.fileManager createDirectoryAtPath:self.diskCachePath
withIntermediateDirectories:YES
attributes:nil
error:NULL];
if (completion) {
// 主队列异步回调block
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
}
- (void)deleteOldFiles {
// 调用下面的方法
[self deleteOldFilesWithCompletionBlock:nil];
}
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
// 串行队列异步调用
dispatch_async(self.ioQueue, ^{
// 获取磁盘缓存目录
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
// 生成来源key数组
NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
// 获取磁盘缓存目录下的内容
NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];
// 获取过期时间
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];
// 创建变量保存缓存文件
NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
// 创建变量保存当前缓存大小
NSUInteger currentCacheSize = 0;
// 创建变量保存要删除的缓存路径
NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
// 遍历枚举对象
for (NSURL *fileURL in fileEnumerator) {
// 获取文件路径下的资源值
NSError *error;
NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
// 如果出错,或者没有资源值,或者是目录就跳过
if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
continue;
}
// 如果资源值的时间比过期时间还要早,就添加到待删链接数组中
NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
[urlsToDelete addObject:fileURL];
continue;
}
// 计算剩下缓存的总大小
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
cacheFiles[fileURL] = resourceValues;
}
// 删除要缓存链接
for (NSURL *fileURL in urlsToDelete) {
[self.fileManager removeItemAtURL:fileURL error:nil];
}
// 如果设置了最大缓存大小,并且当前缓存大小比设置的最大值还要大
if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
// 创建变量定义清理目标为设置缓存最大值的一半
const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;
// 重新排序,按照修改时间的先后顺序,时间最早的在第一个
NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}];
// 遍历缓存
for (NSURL *fileURL in sortedFiles) {
// 移除缓存
if ([self.fileManager removeItemAtURL:fileURL error:nil]) {
// 计算剩余缓存总大小
NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
// 如果剩余缓存总大小达到清理目标大小就停止循环跳出
if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
}
// 主队列异步回调block
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
- (void)backgroundDeleteOldFiles {
// 获取UIApplication对象
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
// 向系统申请时间
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// 清除过期缓存
[self deleteOldFilesWithCompletionBlock:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
- 缓存信息方法
- (NSUInteger)getSize {
// 创建变量保存缓存大小
__block NSUInteger size = 0;
// 串行队列同步执行
dispatch_sync(self.ioQueue, ^{
// 获取磁盘缓存目录的枚举对象
NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath];
// 遍历枚举对象
for (NSString *fileName in fileEnumerator) {
// 获取缓存的磁盘路径
NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
// 获取缓存的大小
NSDictionary<NSString *, id> *attrs = [self.fileManager attributesOfItemAtPath:filePath error:nil];
// 计算总大小
size += [attrs fileSize];
}
});
return size;
}
- (NSUInteger)getDiskCount {
// 创建变量保存缓存大小
__block NSUInteger count = 0;
// 串行队列同步执行
dispatch_sync(self.ioQueue, ^{
// 获取磁盘缓存目录的枚举对象
NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath];
// 获取大小
count = fileEnumerator.allObjects.count;
});
return count;
}
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock {
// 创建磁盘缓存目录url
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
// 串行队列异步执行
dispatch_async(self.ioQueue, ^{
// 创建变量保存文件数量
NSUInteger fileCount = 0;
// 创建变量保存缓存大小
NSUInteger totalSize = 0;
// 获取缓存目录的枚举对象
NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:@[NSFileSize]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];
// 遍历枚举对象
for (NSURL *fileURL in fileEnumerator) {
// 获取文件的大小
NSNumber *fileSize;
[fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
// 计算缓存总大小
totalSize += fileSize.unsignedIntegerValue;
// 计算文件数量
fileCount += 1;
}
// 主队列异步回调block
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(fileCount, totalSize);
});
}
});
}
9.总结
这个类负责图像缓存的处理,分为内存缓存和硬盘缓存,功能包括保存、查询、删除等相关操作。逻辑很清晰,也比较简单。
源码阅读系列:SDWebImage
源码阅读:SDWebImage(二)——SDWebImageCompat
源码阅读:SDWebImage(三)——NSData+ImageContentType
源码阅读:SDWebImage(四)——SDWebImageCoder
源码阅读:SDWebImage(五)——SDWebImageFrame
源码阅读:SDWebImage(六)——SDWebImageCoderHelper
源码阅读:SDWebImage(七)——SDWebImageImageIOCoder
源码阅读:SDWebImage(八)——SDWebImageGIFCoder
源码阅读:SDWebImage(九)——SDWebImageCodersManager
源码阅读:SDWebImage(十)——SDImageCacheConfig
源码阅读:SDWebImage(十一)——SDImageCache
源码阅读:SDWebImage(十二)——SDWebImageDownloaderOperation
源码阅读:SDWebImage(十三)——SDWebImageDownloader
源码阅读:SDWebImage(十四)——SDWebImageManager
源码阅读:SDWebImage(十五)——SDWebImagePrefetcher
源码阅读:SDWebImage(十六)——SDWebImageTransition
源码阅读:SDWebImage(十七)——UIView+WebCacheOperation
源码阅读:SDWebImage(十八)——UIView+WebCache
源码阅读:SDWebImage(十九)——UIImage+ForceDecode/UIImage+GIF/UIImage+MultiFormat
源码阅读:SDWebImage(二十)——UIButton+WebCache
源码阅读:SDWebImage(二十一)——UIImageView+WebCache/UIImageView+HighlightedWebCache