SDWebImage源码解析
SDWebImage
相信大家都不陌生,项目中也经常用到,很久之前我也有阅读源码的习惯,但是一直没有开笔写一写源码解析,怕自己写的不好讲不清楚做了这么一个流程图,还望各位看官海涵🤦♂️,SDWebImage 的基本流程如下(顺便打个水印做个版权保护😂),当前使用的 SDWebImage
版本号是 '5.1.1
;

为了方便读完本文能对SDWebImage 中的其它类也有所了解,特制作了如下思维导图帮助理解SDWebImage 的文件目录(建议可以先看)

项目中的一些SDWebImage 的API ,统一采用简写的方式 ,例如:
//简写前
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
}
//简写后
- (void)sd_setImageWithURL: placeholderImage: options: context: progress: completed:{
}
SDWebImage 常见用法展示
//常见用法
[self.imageview.sd_setImageWithURL:[NSURL URLWithString:URLString] placeholderImage:[UIImage imageNamed:@"placeImage"]];
1.传入URL和占位图以及一些其它参数
如下代码所示意, URL链接最终进入到了UIImageView+WebCache
分类的- (void) sd_setImageWithURL: placeholderImage: options: progress: completed:{}
的方法 ,并继续向下调用 UIView+WebCache
中的- (void)sd_internalSetImageWithURL: placeholderImage: options:context: setImageBlock: progress: completed:
方法 ;
该方法定义在UIView+WebCache
分类中 ,目的是给view添加赋值image对象的功能,方便扩展UIButton、UIImageView等控件添加网络图片;
下面我们接着往下看;
//当前在 UIImageView+WebCache 文件
//调用最底层的 sd_setImageWithURL 方法
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
//最终调用 UIView 分类的sd_internalSetImageWithURL 方法
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
context:context
setImageBlock:nil
progress:progressBlock
completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType, imageURL);
}
}];
}
2. 分析 UIView+WebCache 实现了哪些功能步骤
进入 UIView+WebCache
的 -(void)sd_internalSetImageWithURL: placeholderImage: options:context: setImageBlock: progress: completed:{}
方法中我们可以看到,该函数做了如下事情;
- 如果正在对当前的URL进行下载,则取消
- 如果配置了占位图,设置placeHolderImage
- 开始 ImageIndicator 动画
- 加载图片流程(网络请求 or 本地缓存)
- 给view 的URL属性绑定operation 属性(runtime实现)
由于篇幅原因,部分不重要代码以 省略号...
显示,
//当前在 UIView+WebCache文件中
//获取当前对象的URL key
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
//如果contex 不存在,默认key 为该对象的类名
if (!validOperationKey) {
validOperationKey = NSStringFromClass([self class]);
}
//1.取消正在下载操作的网络图片请求(61行)
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
// 2.设置占位图(64行)
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}
//3.开始加载图片前的loading 动画效果(80行)
[self sd_startImageIndicator];
// 4.开始获取图片操作 SDWebImageManager实现(111行)
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
//获取图片成功进入该block,赋值给当前View
//停止加载动画的loading效果
...
}
//5.给View 的validOperationKey 属性绑定 SDWebImageOperation类型的属性值 (176 行)
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
3. 分析SDWebImageManager 加载图片逻辑
至此,加载图片的操作已经转移给SDWebImageManager
单例来完成,UIView +WebCache
分类,就是完成了给UIView 对象绑定一些自定义对象属性,比如URL对应的key,和判断该View绑定的URL 获取图片的Operaction
对象,方便下次该view 再次请求图片的时候,直接通过属性查找完成;
SDWebImageManager 实现网络加载图片主要用了 这个方法 - (SDWebImageCombinedOperation *)loadImageWithURL: options: context: progress: completed:{ }
//当前在 SDWebImageManager 文件中
//真正 加载网络图片的操作是在这个函数中
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock {
省略一部分代码...
//该代码块用来判断该URL是否是之前请求失败的地址
//如果是失败的地址,return 一个operation 出来,结束
BOOL isFailedUrl = NO;
if (url) {
SD_LOCK(self.failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url];
SD_UNLOCK(self.failedURLsLock);
}
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}] url:url];
return operation;
}
// 这个才是真正要关注的逻辑(加载网络请求加载图片)or(加载之前缓存的本地的图片)
[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
return operation;
}
4 分析- (void)callCacheProcessForOperation: url: options: context: progress:completed: {}
是如何实现 网络请求
和获取本地缓存
分析- (void)callCacheProcessForOperation: url: options: context: progress:completed: {}
方法是如何实现 网络请求
和获取本地缓存
这两种情况的;
// Query cache process
- (void)callCacheProcessForOperation: url: options: context:progress: completed: {
//通过配置,判断是否查找缓存
BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
if (shouldQueryCache) {
//1.第一种情况查找缓存的情况
operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
此处省略部分代码...
if(!operation || operation.isCancelled ) {
//如operation 被取消 ,则 return
return
}
else{
//继续从网络请求资源图片
//调用 self callDownloadProcessForOperation:operation方法
}
}];
} else {
//2.第二种获取网络图片
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
}
}
4.1 获取本地缓存的资源图片
查询本地缓存通过self.imageCache
这个属性来操作 ,imageCache
是一个 [SDImageCache sharedImageCache]
单例实现的;
最后跟踪 到 SDImageCache
类中的 - (NSOperation *)queryCacheOperationForKey: options: context: done: {}
方法比较关键
// SDImageCache.m内部
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
省略部分代码...
//1.通过内存获UIImage 的方法 (378行)
UIImage *image = [self imageFromMemoryCacheForKey:key];
//2.此处定义block 代码块,通过磁盘查找图片数据
@autoreleasepool {
void(^queryDiskBlock)(void) = ^{
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
if(image){
//此处为从内存中取到图片数据的逻辑(处理缓存类型配置)
}else if(diskData){
//此处为从磁盘本地获取图片数据后的操作(处理缓存类型配置)
}
}
}
//3.分别同步或者异步执行block查找磁盘的图片数据
if (shouldQueryDiskSync) {
dispatch_sync(self.ioQueue, queryDiskBlock);
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}
}
较为关键的两个属性 self.memoryCache
和 self.diskCache
分别实现了如如何从内存中获取图片和从硬盘中获取图片;
分别是SDMemoryCache
类型 和 SDDiskCache
类型,具体实现细节,可以找到该类查看;
// SDImageCache.m内部
//通过内存获取图片数据的操作(290行)
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
return [self.memoryCache objectForKey:key];
}
// 通过磁盘获取代码的操作(316行)
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
其它代码省略....
NSData *data = [self.diskCache dataForKey:key];
return data;
}
4.2 获取网络图片
获取网络图片通过self.imageLoader
这个属性来操作 , imageLoader
是一个[SDWebImageDownloader sharedDownloader]
单例实现的;
//SDWebImageManager.m 文件内
//加载网络上的图片资源的操作 (310行)
- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(SDWebImageContext *)context
cachedImage:(nullable UIImage *)cachedImage
cachedData:(nullable NSData *)cachedData
cacheType:(SDImageCacheType)cacheType
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
省略部分代码...
//通过self.imageLoader 获取当前请求的operation
operation.loaderOperation = [self.imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
省略逻辑代码代码...
//在这里拿到图片的逻辑
//或者网络图片请求失败的逻辑
}];
//最后将operation 从self.runningOperations 数组中移除
[self safelyRemoveOperationFromRunning:operation];
}
网络请求的具体操作由SDWebImageDownloader
实现,具体实现可查看其相关类,SD的源码写的真的的挺好,作者的架构思路值得学习
至此SDWebImage
的源码流程分析已经告一段落,由于篇幅原因,暂时讲解至此;