iOS】------ SDWebImage源码学习
SDWebImage
SDWebImage具有缓存支持的异步映像下载程序。并添加了像UI元素分类类UIImageView、UIButton、MKAnnotationView,可以直接为这些UI元素添加图片。
基础使用
在日常的使用中,通常是加载网络图片到UIImageView上展示,所以一般在需要使用SDWebImage的文件中只引用#import "UIImageView+WebCache.h"头文件。
最简单的加载方式是只加载图片地址:
objectivec
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:imageView];
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]];
SDWebImage也提供了其他的加载方法,不过点击方法进入查看后,发现最终都是调用其全能方法:
objectivec
- (void)sd_setImageWithURL:(nullable NSURL *)url {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}
全能方法除了必需的的图片地址,还提供了占位图、可选项、加载进度和完成回调。
objectivec
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
占位图像: 在网络图片加载过程中,ImageView会显示占位图像,给用户一个视觉上的暂时反馈。当网络图片加载完成后,ImageView中会显示加载完成的图片。
选项: 是一个枚举类型的值,用于设置加载图片的选项。其中包含了一些常用的选项,例如缓存策略、图片解码方式、加载优先级等。通过传递不同的选项,可以对图片加载的行为进行定制。
进度: 它们提供了对图片加载过程中的进度和结果的反馈。它们提供了对图片加载过程中的进度和结果的反馈。
完成回调: 是一个块对象,用于在图片加载完成后执行相应的操作。
点击进入全能方法中:
objectivec
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:nil
setImageBlock:nil
progress:progressBlock
completed:completedBlock];
}
全能方法并没有什么实际的实现,只是对另一个方法的封装。
主要功能
- 对UIImageView、UIButton、MKAnnotationView添加Web图像和告诉缓存管理。
- 异步图像下载器。
- 具有自动缓存到期处理的异步内存+磁盘映像缓存。
- 背景图像解压缩。
- 对动画图像的支持。
- 可以自定义和组合的转换,可在下载后立即应用于图像。
- 可以自定义加载器(如照片库)来扩展图像加载功能。
- 加载中的indicator显示。
- 保证不会下载相同的URL。
- 下载过程或者资源保存过程用到了GCD和ARC。
- 提前将获取到的图片放到主线程,保证不会阻塞主线程。
获取图片缓存
在图片加载的方法实现中,可以看到有比较重要的两个方法:一个是获取图片缓存,另一个是从网络下载图片。 在这一节,我们先看获取图片缓存:
objectivec
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock;
点击方法进入查看其实现:
objectivec
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock {
// 如果没传参数key就直接回调并返回,就不继续向下执行了
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// 先根据key查找内存中是否有缓存
UIImage *image = [self imageFromMemoryCacheForKey:key];
// 如果有缓存图片,并且没设置强制从硬盘中查找缓存,就直接回调并返回了
BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryDataWhenInMemory));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}
// 生成一个操作对象
NSOperation *operation = [NSOperation new];
// 生成一个查询硬盘缓存的代码块
void(^queryDiskBlock)(void) = ^{
// 如果操作取消就直接返回,不执行回调
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
// 生成一个自动释放池
@autoreleasepool {
// 根据key查找硬盘中是否有缓存
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);
});
}
}
}
};
// 如果设置了同步查询硬盘缓存的选项就直接调用,否则就自定义串行队列异步回调
if (options & SDImageCacheQueryDiskSync) {
queryDiskBlock();
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation;
}
获取图片缓存的逻辑还是很清晰的:
- 首先查找在内存中的缓存,再根据设置的选项决定要不要继续查找。
- 然后根据设置的选项决定是同步还是异步查找硬盘中的缓存。
- 接着根据设置的选项决定要不要把硬盘中的缓存图片缓存到内存中。
- 最后进行回调数据。
缓存机制
独立的异步图像下载
可能会用到单独的异步图片下载,则一定要用- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
来建立一个SDWebImageDownLoader 的实例。这样就可以有下载进度的回调和下载完成的回调,可以在回调完成进度条相关的操作和显示图片相关的操作。
独立的异步图像缓存
SDImageCache类提供一个管理缓存的单例类。
objectivec
SDImageCache *imageCache = [SDImageCache sharedImageCache];
查找和缓存图片时以URL作为key。(先查找内存,如果内存不存在该图片,再查找硬盘;查找硬盘时,以URL的MD5值作为key)。
查找机制如下:
- Memory(内存)中查找:SDImageCache 类的 queryDiskCacheForKey 方法用于查询图片缓存。queryDiskCacheForKey 方法先会查询 Memory Cache ,如果查找到就直接返回,反之进入下面的硬盘查找。
- Disk(磁盘) 中查找:如果 Memory Cache 查找不到, 就会查询 Disk Cache。就是如果 Disk Cache 查询成功,会把得到的图片再次设置到 Memory Cache 中, 以便最大化那些高频率展现图片的效率。如果找不到就进入下面的网络下载。
缓存机制如下:
- 内存缓存的处理是使用 NSCache 对象来实现的。NSCache 是一个类似于集合的容器。它存储 key-value 对,这一点类似于 NSDictionary 类。 我们通常用使用缓存来临时存储短时间使用但创建昂贵的对象。重用这些对象可以优化性能,因为它们的值不需要重新计算。另外一方面,这些对象对于程序来说不是紧要的,在内存紧张时会被丢弃,所以有Disk(硬盘)缓存清理策略。
- SDWebImage 使用 NSFileManager 对象来实现磁盘缓存,图片存储的位置位于Cache文件夹。另外,SDImageCache 还定义了一个串行队列,来异步存储图片。 SDImageCache 提供了大量方法来缓存、获取、移除及清空图片。而对于每个图片,为了方便地在内存或磁盘中对它进行这些操作,我们需要一个 key 值来索引它。 在内存中,我们将其作为 NSCache 的 key 值,而在磁盘中,我们用这个 key 作为图片的文件名。 对于一个远程服务器下载的图片,其 url 理所当然作为这个 key 值。
核心类:
- SDWebImageDownloader: 负责维持图片的下载队列,是一个单例对象
- SDWebImageDownloaderOperation: 负责真正的图片下载请求,一个自定义的并行Operation子类
- SDImageCache: 负责SDWebImage的缓存工作,是一个单例对象
- SDWebImageManager: 是总的管理类,维护了一个SDWebImageDownloader实例和一个SDImageCache实例,是下载与缓存的桥梁
- SDWebImageDecoder: 负责图片的解压缩
- SDWebImagePrefetcher: 负责图片的预取
- UIImageView+WebCache: 和其他的扩展都是与用户直接打交道的。
概念框架
- UIImageView+WebCache和UIButton+WebCache直接为表层的UIKit框架提供接口。
- SDWebImageManger负责处理和协调SDWebImageDownloader和SDWebImageCache,并与 UIKit层进行交互。
- SDWebImageDownloaderOperation真正执行下载请求,最底层的两个类为高层抽象提供支持。
这段时间主要先梳理源码的框架,以及源码的基础内容,没有时间对源码进行深入解读。