SDWebImage源码阅读2

SDImageCache

SDImageCache 是 SDWebImage 中用来处理缓存的类,他是一个单例

SDWebImage 中关于缓存可以分为磁盘缓存 id<SDDiskCache> 和 内存缓存 id<SDMemoryCache>

1
2
3
4
5
6
7
8
9
10
@interface SDImageCache ()

#pragma mark - Properties
@property (nonatomic, strong, readwrite, nonnull) id<SDMemoryCache> memoryCache; // 内存缓存管理
@property (nonatomic, strong, readwrite, nonnull) id<SDDiskCache> diskCache; // 磁盘缓存管理
@property (nonatomic, copy, readwrite, nonnull) SDImageCacheConfig *config; // 基本设置
@property (nonatomic, copy, readwrite, nonnull) NSString *diskCachePath; // 磁盘缓存路径
@property (nonatomic, strong, nullable) dispatch_queue_t ioQueue; // io队列

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// 查询缓存操作
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {

//不存在这个key,就调用完成block
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}

// 是否有转换操作,获取转换操作完成后图片的key
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
if (transformer) {
// grab the transformed disk image if transformer provided
NSString *transformerKey = [transformer transformerKey];
key = SDTransformedKeyForKey(key, transformerKey);
}

// 先检查内存是否存在该图片
UIImage *image = [self imageFromMemoryCacheForKey:key];

if (image) {
if (options & SDImageCacheDecodeFirstFrameOnly) {
// Ensure static image
Class animatedImageClass = image.class;
if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) {
#if SD_MAC
image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
#else
image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
#endif
}
} else if (options & SDImageCacheMatchAnimatedImageClass) {
// Check image class matching
Class animatedImageClass = image.class;
Class desiredImageClass = context[SDWebImageContextAnimatedImageClass];
if (desiredImageClass && ![animatedImageClass isSubclassOfClass:desiredImageClass]) {
image = nil;
}
}
}

// 是否只查找内存缓存
BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryMemoryData));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}

// 2.查找内存缓存
NSOperation *operation = [NSOperation new];
// Check whether we need to synchronously query disk
// 1. in-memory cache hit & memoryDataSync
// 2. in-memory cache miss & diskDataSync
BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
(!image && options & SDImageCacheQueryDiskDataSync));
void(^queryDiskBlock)(void) = ^{
if (operation.isCancelled) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return;
}

@autoreleasepool {
///如果盘中存在图片,并且如果可以使用缓存
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
SDImageCacheType cacheType = SDImageCacheTypeNone;
if (image) {
// the image is from in-memory cache, but need image data
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
cacheType = SDImageCacheTypeDisk;
// decode image data only if in-memory cache missed
diskImage = [self diskImageForKey:key data:diskData options:options context:context];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
//将硬盘中的图片在缓存中存一份
[self.memoryCache setObject:diskImage forKey:key cost:cost];
}
}

if (doneBlock) {
if (shouldQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
}
}
};

// Query in ioQueue to keep IO-safe
if (shouldQueryDiskSync) {
dispatch_sync(self.ioQueue, queryDiskBlock);
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}

return operation;
}

先看一下 SDImageCacheConfig

SDImageCacheConfig 是一个关于缓存设置相关的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (instancetype)init {
if (self = [super init]) {
_shouldDisableiCloud = YES; // 忽略iCloud
_shouldCacheImagesInMemory = YES; // 缓存到内存
_shouldUseWeakMemoryCache = YES; // 缓存到磁盘
_shouldRemoveExpiredDataWhenEnterBackground = YES; // 进入后台模式删除过期的数据
_diskCacheReadingOptions = 0;
_diskCacheWritingOptions = NSDataWritingAtomic;
_maxDiskAge = kDefaultCacheMaxDiskAge; // 磁盘缓存最大存储时间 1WEEK
_maxDiskSize = 0; // 最大磁盘缓存大小 0表示无上限
_diskCacheExpireType = SDImageCacheConfigExpireTypeModificationDate;
_memoryCacheClass = [SDMemoryCache class];
_diskCacheClass = [SDDiskCache class];
}
return self;
}

SDMemoryCache 内存缓存

SDMemoryCache 继承自 NSCache,实现了 SDMemoryCache 协议,封装了一系列方法,便于扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@interface SDMemoryCache <KeyType, ObjectType> : NSCache <KeyType, ObjectType> <SDMemoryCache>

@property (nonatomic, strong, nonnull, readonly) SDImageCacheConfig *config;

@end

@interface SDMemoryCache <KeyType, ObjectType> ()

@property (nonatomic, strong, nullable) SDImageCacheConfig *config;
#if SD_UIKIT
@property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; // strong-weak cache
@property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock; // a lock to keep the access to `weakCache` thread-safe
#endif
@end

SDMemoryCache 也使用 NSMapTable 来储存缓存的数据,并加了锁来保证线程安全

那为什么 SDMemoryCache 继承自 NSCache 还要维护一个 NSMapTable 呢,因为 NSCache 对于 value 是强引用的,而 NSMapTable 对于value 是弱引用的!

可以通过config 的 shouldUseWeakMemoryCache 来关闭使用 NSMapTable 管理。

SDDiskCache 磁盘缓存

SDMemoryCache 实现了 SDDiskCache 协议,封装了一系列方法,便于扩展



-------------The End-------------

本文标题:SDWebImage源码阅读2

文章作者:kysonyangs

发布时间:2018年12月22日 - 15:12

最后更新:2020年05月17日 - 20:05

原始链接:https://kysonyangs.github.io/default/SDWebImage源码阅读2/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。