SDWebImageCache管理着SDWebImage的缓存,其中内存缓存采用NSCache,同时会创建一个ioQueue负责对硬盘的读写,并且会添加观察者,在收到内存警告、关闭或进入后台时完成对应的处理:
1 | - (id)init { |
查询图片
每次向SDWebImageCache索取图片的时候,会先根据图片URL对应的key值先检查内存中是否有对应的图片,如果有则直接返回;如果没有则在ioQueue中去硬盘中查找,其中文件名是是根据URL生成的MD5值,找到之后先将图片缓存在内存中,然后在把图片返回: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- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
/*...*/
// 首先查找内存缓存
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
doneBlock(image, SDImageCacheTypeMemory);
return nil;
}
//硬盘查找
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
//创建自动释放池,内存及时释放
@autoreleasepool {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage) {
CGFloat cost = diskImage.size.height * diskImage.size.width * diskImage.scale * diskImage.scale;
//缓存到NSCache中
[self.memCache setObject:diskImage forKey:key cost:cost];
}
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, SDImageCacheTypeDisk);
});
}
});
return operation;
}
在硬盘查询的时候,会在后台将NSData转成UIImage,并完成相关的解码工作:1
2
3
4
5
6
7
8
9
10
11
12
13
14- (UIImage *)diskImageForKey:(NSString *)key {
NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
if (data) {
UIImage *image = [UIImage sd_imageWithData:data];
image = [self scaledImageForKey:key image:image];
if (self.shouldDecompressImages) {
image = [UIImage decodedImageWithImage:image];
}
return image;
}
else {
return nil;
}
}
保存图片
当下载完图片后,会先将图片保存到NSCache中,并把图片像素大小作为该对象的cost值,同时如果需要保存到硬盘,会先判断图片的格式,PNG或者JPEG,并保存对应的NSData到缓存路径中,文件名为URL的MD5值:1
2
3
4
5
6
7
8
9
10
11
12
13- (NSString *)cachedFileNameForKey:(NSString *)key {
//根据key生成对应的MD5值作为文件名
const char *str = [key UTF8String];
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
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]];
return filename;
}
1 | - (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk |
硬盘文件的管理
在程序退出或者进入后台时,会出图片文件进行管理,具体的策略:
- 清除过期的文件,默认一星期
- 如果设置了最大缓存,并且当前缓存的文件超过了这个限制,则删除最旧的文件,直到当前缓存文件的大小为最大缓存大小的一半
1 | - (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock { |
总结
- 接口设计简单
通常我们使用较多的UIImageView分类:1
2[self.imageView sd_setImageWithURL:[NSURL URLWithString:@"url"]
placeholderImage:[UIImage imageNamed:@"placeholder"]];
一个简单的接口将其中复杂的实现细节全部隐藏:简单就是美。
- 采用NSCache作为内存缓
- 耗时较长的请求,都采用异步形式,在回调函数块中处理请求结果
- NSOperation和NSOperationQueue:可以取消任务处理队列中的任务,设置最大并发数,设置operation之间的依赖关系。
- 图片缓存清理的策略
- dispatch_barrier_sync:前面的任务执行结束后它才执行,而且它后面的任务要等它执行完成之后才会执行。
- 使用weak self strong self 防止retain circle
- 如果子线程进需要不断处理一些事件,那么设置一个Run Loop是最好的处理方式