我们先来看UIKit层,最外层的UIImageView+WebCache层给我们提供了很多接口:
//UIImageView+WebCache
- (void)sd_setImageWithURL:(nullable NSURL *)url {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
}
- (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];
}
- (void)sd_setImageWithPreviousCachedImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:url];
UIImage *lastPreviousCachedImage = [[SDImageCache sharedImageCache] imageFromCacheForKey:key];
[self sd_setImageWithURL:url placeholderImage:lastPreviousCachedImage ?: placeholder options:options progress:progressBlock completed:completedBlock];
}
这些方法最后都会调用到:
//UIImageView+WebCache
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
继续往下走,到了UIView+WebCache层:
//UIView+WebCache
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
return [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options operationKey:operationKey setImageBlock:setImageBlock progress:progressBlock completed:completedBlock context:nil];
}
//参数一:url 图像的url。
//参数二:placeholder 最初要设置的图像,直到图像请求完成。
//参数三:options 下载图像时要使用的选项。
//参数四:context 上下文包含用于执行指定更改或过程的不同选项
//参数五:setImageBlock 块用于自定义设置图像代码。
//参数六:progressBlock 下载图像时调用的块。进度块是在后台队列上执行的。
/*这个块没有返回值,并以请求的UIImage作为第一个参数,NSData表示作为第二个参数。
如果发生错误,image参数为nil,第三个参数可能包含一个NSError。
第四个参数是一个“SDImageCacheType”enum,表示图像是从本地缓存、内存缓存还是网络检索到的。
第五个参数通常总是“YES”。但是,如果你提供SDWebImageAvoidAutoSetImage与SDWebImageProgressiveLoad选项,以启用渐进下载和设置自己的映像。因此,对部分图像重复调用该块。当图像完全下载后,最后一次调用该块,并将最后一个参数设置为YES。
最后一个参数是原始图像URL。*/
为什么会调用到UIView+WebCache?
因外SDWebImage不仅仅支持UIImageView的图片加载,还同样支持UIButton等控件的加载。
接下来看看这个函数的实现过程:
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
if (context) {
//获取枚举选项
// copy to avoid mutable object
// 复制以避免可变对象
context = [context copy];
} else {
context = [NSDictionary dictionary];
}
//获取一个有效的操作的对应键值,这里获取的是"设置图片操作的键"
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
//如果传递的context中没有该图片操作键,就新建一个该类的操作键赋给context
if (!validOperationKey) {
// pass through the operation key to downstream, which can used for tracing operation or image view class
// 将操作键传递给下游,可用于跟踪操作或图像视图类
validOperationKey = NSStringFromClass([self class]);
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
context = [mutableContext copy];
}
//将当前操作键赋值给 sd_latestOperationKey属性,操作键用于标识一个视图实例的不同查询(如UIButton)。
self.sd_latestOperationKey = validOperationKey;
//下面这行代码是保证没有当前正在进行的异步下载操作, 使它不会与即将进行的操作发生冲突
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
//将传递过来的url赋值给 sd_imageURL属性
//注意,由于类别的限制,如果直接使用setImage:,这个属性可能会不同步。
self.sd_imageURL = url;
//添加临时的占位图(在不延迟添加占位图的option下)
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}
if (url) {
// reset the progress
//重置进程
NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
if (imageProgress) {
imageProgress.totalUnitCount = 0;
imageProgress.completedUnitCount = 0;
}
#if SD_UIKIT || SD_MAC //环境判断
// check and start image indicator
// 检查并启动图像指示灯
[self sd_startImageIndicator]; //开启图像指示器
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
//self.sd_imageIndicator
//图像加载期间的图像指示器。如果你不需要指示器,请指定nil。默认为零
//这个设置将移除旧的指示器视图,并将新的指示器视图添加到当前视图的子视图中。
//@注意,因为这是UI相关的,你应该只从主队列访问。
#endif //环境判断
//从context字典中获取该键对应的数据
//SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager";
SDWebImageManager *manager = context[SDWebImageContextCustomManager];
//如果不存在,就新创建一个单例
if (!manager) {
manager = [SDWebImageManager sharedManager];
} else {
// remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager)
// 删除manager以避免循环引用(manger -> loader -> operation -> context -> manager)
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextCustomManager] = nil;
context = [mutableContext copy];
}
//--------------SDImageLoaderProgressBlock-----------------//
SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
if (imageProgress) {
//项目总数
imageProgress.totalUnitCount = expectedSize;
//已完成数
imageProgress.completedUnitCount = receivedSize;
}
#if SD_UIKIT || SD_MAC//环境判断
//重写并更新进度
if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) {
double progress = 0;
if (expectedSize != 0) {
progress = (double)receivedSize / expectedSize;
}
progress = MAX(MIN(progress, 1), 0); // 0.0 - 1.0
//在主线程不等待执行
dispatch_async(dispatch_get_main_queue(), ^{
[imageIndicator updateIndicatorProgress:progress];
});
}
#endif //环境判断
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};
//--------------SDImageLoaderProgressBlock-----------------//
@weakify(self);
//SDWebImageManager下载图片
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) {
@strongify(self);
//如果不存在就直接,结束函数了。
if (!self) { return; }
// if the progress not been updated, mark it to complete state
// 如果进度没有更新,将其标记为完成状态
if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
#if SD_UIKIT || SD_MAC
// check and stop image indicator
// 检查和停止图像指示灯,如果完成了就停止
if (finished) {
[self sd_stopImageIndicator];
}
#endif
//是否应该调用CompletedBlock,通过options和状态的&运算决定
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
//是否应该不设置Image,通过options和状态的&运算决定
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
//调用CompletedBlock的Block
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (!self) { return; }
if (!shouldNotSetImage) {
[self sd_setNeedsLayout];
}
//判断是否需要调用completedBlock
if (completedBlock && shouldCallCompletedBlock) {
//调用completedBlock,image,而且不自动替换 placeholder image
completedBlock(image, data, error, cacheType, finished, url);
}
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set;我们得到了一个图像,但是SDWebImageAvoidAutoSetImage标志被设置了
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set;我们没有得到图像,SDWebImageDelayPlaceholder没有设置
if (shouldNotSetImage) { //如果应该不设置图像
//主线程不等待安全的调用上述创建的callCompletedBlockClojure
dispatch_main_async_safe(callCompletedBlockClojure);
return;//结束函数
}
UIImage *targetImage = nil;//目标图像
NSData *targetData = nil;//目标数据
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
//我们得到一个图像,SDWebImageAvoidAutoSetImage没有设置
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
//我们没有图像,并且设置了SDWebImageDelayPlaceholder标志
targetImage = placeholder;
targetData = nil;
}
#if SD_UIKIT || SD_MAC
// 检查是否使用图像过渡
SDWebImageTransition *transition = nil;
BOOL shouldUseTransition = NO;//是否使用过滤属性
if (options & SDWebImageForceTransition) {//强制转换
// 总是
shouldUseTransition = YES;
} else if (cacheType == SDImageCacheTypeNone) { //类型不可用
// 从网络
shouldUseTransition = YES;
} else {
// From disk (and, user don't use sync query)
// 从磁盘(并且,用户不使用同步查询)
if (cacheType == SDImageCacheTypeMemory) { //内存
shouldUseTransition = NO;
} else if (cacheType == SDImageCacheTypeDisk) { //磁盘
if (options & SDWebImageQueryMemoryDataSync || options & SDWebImageQueryDiskDataSync) {
shouldUseTransition = NO;
} else {
shouldUseTransition = YES;
}
} else {
// Not valid cache type, fallback
// 缓存类型无效,请回退
shouldUseTransition = NO;
}
}
//完成并且应该使用转换
if (finished && shouldUseTransition) {
transition = self.sd_imageTransition; //转换属性,图片加载完成后的图片转换
}
#endif
//dispatch_main_async_safe : 保证block能在主线程安全进行,不等待
dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
//马上替换 placeholder image
[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
//马上替换 placeholder image
[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif
//使用上述创建的block变量,调用CompletedBlock
callCompletedBlockClojure();
});
}];
//----------operation----------//
//在操作缓存字典(operationDictionary)里添加operation,表示当前的操作正在进行
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
//-------------------------url存在---------------------------//
} else {
//-------------------------url不存在---------------------------//
#if SD_UIKIT || SD_MAC
//url不存在立马停止图像指示器
[self sd_stopImageIndicator];
#endif
//如果url不存在,就在completedBlock里传入error(url为空)
dispatch_main_async_safe(^{
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
}
});
}
//-------------------------url不存在---------------------------//
}
我们来分开分别看一下:
if (context) {
//获取枚举选项
// copy to avoid mutable object
// 复制以避免可变对象
context = [context copy];
} else {
context = [NSDictionary dictionary];
}
//获取一个有效的操作的对应键值,这里获取的是"设置图片操作的键"
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
//如果传递的context中没有该图片操作键,就新建一个该类的操作键赋给context
if (!validOperationKey) {
// pass through the operation key to downstream, which can used for tracing operation or image view class
// 将操作键传递给下游,可用于跟踪操作或图像视图类
validOperationKey = NSStringFromClass([self class]);
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
context = [mutableContext copy];
}
//将当前操作键赋值给 sd_latestOperationKey属性,操作键用于标识一个视图实例的不同查询(如UIButton)。
self.sd_latestOperationKey = validOperationKey;
//下面这行代码是保证没有当前正在进行的异步下载操作, 使它不会与即将进行的操作发生冲突
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
其中在这一部分调用了UIView+WebCacheOperation中的方法,我们来看看它是怎么实现的:
//设置图像加载操作,使用一个字典存储key和对应的操作
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
//key存在
if (key) {
//取消当前UIView和key的所有操作,key——标识操作的键
[self sd_cancelImageLoadOperationWithKey:key];
//操作存在
if (operation) {
//获取operationDictionary字典
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
//创建一个互斥锁,保证此时没有其它线程对self对象进行修改,然后再添加key和对应的operation(操作)
@synchronized (self) {
[operationDictionary setObject:operation forKey:key];
}
}
}
}
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
if (key) {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
id<SDWebImageOperation> operation;
@synchronized (self) {
operation = [operationDictionary objectForKey:key];
}
if (operation) {
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
[operation cancel];
}
@synchronized (self) {
[operationDictionary removeObjectForKey:key];
}
}
}
}
实际上,所有的操作都是由一个实际上,所有的操作都是由一个operationDictionary字典维护的,执行新的操作之前,cancel所有的operation。
为什么不直接在UIImageView+WebCache里直接关联这个对象呢?我觉得这里作者应该是遵从面向对象的单一职责原则(SRP:Single responsibility principle),就连类都要履行这个职责,何况分类呢?这里作者专门创造一个分类UIView+WebCacheOperation来管理操作缓存(字典)。
//2.设置占位图
//将传递过来的url赋值给 sd_imageURL属性
//注意,由于类别的限制,如果直接使用setImage:,这个属性可能会不同步。
self.sd_imageURL = url;
//添加临时的占位图(在不延迟添加占位图的option下)
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}
//3.判断URL是否合法
if (url) {
// reset the progress
//重置进程
NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
if (imageProgress) {
imageProgress.totalUnitCount = 0;
imageProgress.completedUnitCount = 0;
}
#if SD_UIKIT || SD_MAC //环境判断
// check and start image indicator
// 检查并启动图像指示灯
[self sd_startImageIndicator]; //开启图像指示器
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
//self.sd_imageIndicator
//图像加载期间的图像指示器。如果你不需要指示器,请指定nil。默认为零
//这个设置将移除旧的指示器视图,并将新的指示器视图添加到当前视图的子视图中。
//@注意,因为这是UI相关的,你应该只从主队列访问。
#endif //环境判断
//从context字典中获取该键对应的数据
//SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager";
SDWebImageManager *manager = context[SDWebImageContextCustomManager];
//如果不存在,就新创建一个单例
if (!manager) {
manager = [SDWebImageManager sharedManager];
} else {
// remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager)
// 删除manager以避免循环引用(manger -> loader -> operation -> context -> manager)
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextCustomManager] = nil;
context = [mutableContext copy];
}
//--------------SDImageLoaderProgressBlock-----------------//
SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
if (imageProgress) {
//项目总数
imageProgress.totalUnitCount = expectedSize;
//已完成数
imageProgress.completedUnitCount = receivedSize;
}
#if SD_UIKIT || SD_MAC//环境判断
//重写并更新进度
if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) {
double progress = 0;
if (expectedSize != 0) {
progress = (double)receivedSize / expectedSize;
}
progress = MAX(MIN(progress, 1), 0); // 0.0 - 1.0
//在主线程不等待执行
dispatch_async(dispatch_get_main_queue(), ^{
[imageIndicator updateIndicatorProgress:progress];
});
}
#endif //环境判断
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};
//--------------SDImageLoaderProgressBlock-----------------//
@weakify(self);
//SDWebImageManager下载图片
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) {
@strongify(self);
//如果不存在就直接,结束函数了。
if (!self) { return; }
// if the progress not been updated, mark it to complete state
// 如果进度没有更新,将其标记为完成状态
if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
#if SD_UIKIT || SD_MAC
// check and stop image indicator
// 检查和停止图像指示灯,如果完成了就停止
if (finished) {
[self sd_stopImageIndicator];
}
#endif
//是否应该调用CompletedBlock,通过options和状态的&运算决定
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
//是否应该不设置Image,通过options和状态的&运算决定
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
//调用CompletedBlock的Block
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (!self) { return; }
if (!shouldNotSetImage) {
[self sd_setNeedsLayout];
}
//判断是否需要调用completedBlock
if (completedBlock && shouldCallCompletedBlock) {
//调用completedBlock,image,而且不自动替换 placeholder image
completedBlock(image, data, error, cacheType, finished, url);
}
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set;我们得到了一个图像,但是SDWebImageAvoidAutoSetImage标志被设置了
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set;我们没有得到图像,SDWebImageDelayPlaceholder没有设置
if (shouldNotSetImage) { //如果应该不设置图像
//主线程不等待安全的调用上述创建的callCompletedBlockClojure
dispatch_main_async_safe(callCompletedBlockClojure);
return;//结束函数
}
UIImage *targetImage = nil;//目标图像
NSData *targetData = nil;//目标数据
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
//我们得到一个图像,SDWebImageAvoidAutoSetImage没有设置
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
//我们没有图像,并且设置了SDWebImageDelayPlaceholder标志
targetImage = placeholder;
targetData = nil;
}
#if SD_UIKIT || SD_MAC
// 检查是否使用图像过渡
SDWebImageTransition *transition = nil;
BOOL shouldUseTransition = NO;//是否使用过滤属性
if (options & SDWebImageForceTransition) {//强制转换
// 总是
shouldUseTransition = YES;
} else if (cacheType == SDImageCacheTypeNone) { //类型不可用
// 从网络
shouldUseTransition = YES;
} else {
// From disk (and, user don't use sync query)
// 从磁盘(并且,用户不使用同步查询)
if (cacheType == SDImageCacheTypeMemory) { //内存
shouldUseTransition = NO;
} else if (cacheType == SDImageCacheTypeDisk) { //磁盘
if (options & SDWebImageQueryMemoryDataSync || options & SDWebImageQueryDiskDataSync) {
shouldUseTransition = NO;
} else {
shouldUseTransition = YES;
}
} else {
// Not valid cache type, fallback
// 缓存类型无效,请回退
shouldUseTransition = NO;
}
}
//完成并且应该使用转换
if (finished && shouldUseTransition) {
transition = self.sd_imageTransition; //转换属性,图片加载完成后的图片转换
}
#endif
//dispatch_main_async_safe : 保证block能在主线程安全进行,不等待
dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
//马上替换 placeholder image
[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
//马上替换 placeholder image
[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif
//使用上述创建的block变量,调用CompletedBlock
callCompletedBlockClojure();
});
}];
//----------operation----------//
//在操作缓存字典(operationDictionary)里添加operation,表示当前的操作正在进行
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
//-------------------------url存在---------------------------//
} else {
//-------------------------url不存在---------------------------//
#if SD_UIKIT || SD_MAC
//url不存在立马停止图像指示器
[self sd_stopImageIndicator];
#endif
//如果url不存在,就在completedBlock里传入error(url为空)
dispatch_main_async_safe(^{
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
}
});
}
//-------------------------url不存在---------------------------//
上文提到过,SDWebImageManager同时管理SDImageCache和SDWebImageDownloader两个类,它是这一层的老大哥。在下载任务开始的时候,SDWebImageManager首先访问SDImageCache来查询是否存在缓存,如果有缓存,直接返回缓存的图片。如果没有缓存,就命令SDWebImageDownloader来下载图片,下载成功后,存入缓存,显示图片。以上是SDWebImageManager大致的工作流程。
直接找到loadImageWithURL方法,这个方法主要是对url的一些判断,context与options的预处理,内容如下:
a. 先判断url的可行性
b. 对context,options进行预处理,并放到result里面
c. 调用callCacheProcessForOperation 判断是否有缓存,如果有则进入ImageCache 拿到缓存数据,如果没有则进入callDownloadProcessForOperation 方法进一步判断如何下载
先看看这些步骤的源码,看完再看callCacheProcessForOperation做了些什么
SDWebImageManager属性
// ============== SDWebImageManager.m的扩展属性声明部分 ============== //
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache; //管理缓存
@property (strong, nonatomic, readwrite, nonnull) id<SDImageLoader> imageLoader; //下载器imageLoader;
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs; //记录失效url的名单
@property (strong, nonatomic, nonnull) NSMutableSet<SDWebImageCombinedOperation *> *runningOperations; //记录当前正在执行的操作
SDWebImageManager下载图片的方法只有一个:
- (nullable SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock;
//如果缓存中没有图像,则在给定URL下载图像,否则返回缓存版本。
//参数一:url 图像的URL
//参数二:options 指定此请求使用的选项的掩码
//参数三:context 上下文包含不同的选项来执行指定的更改或流程,参见' SDWebImageContextOption '。这将保存'options'枚举不能保存的额外对象。
//参数四:progressBlock 下载图像时调用的块,进度块在后台队列上执行
//参数五:completedBlock 当操作完成时调用的块
//必选参数
/* 这个块没有返回值,并以请求的UIImage作为第一个参数,NSData表示作为第二个参数。
* 如果发生错误,image参数为nil,第三个参数可能包含一个NSError。
* 第四个参数是一个“SDImageCacheType”枚举,指示是否从本地缓存检索到图像或者从内存缓存或者网络。
* 当使用SDWebImageProgressiveLoad选项并下载镜像时,第五个参数设置为NO。因此,对部分图像重复调用该块。当图像完全下载后,最后一次调用该块,并将最后一个参数设置为YES。
* 最后一个参数是原始图像URL
//return 返回一个SDWebImageCombinedOperation的实例,你可以取消加载过程。
我们来看下它的实现:
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
// 没有completedBlock调用这个方法是没有意义的
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
//很常见的错误是使用NSString对象而不是NSURL发送URL。由于一些奇怪的原因,Xcode不会对这种类型不匹配抛出任何警告。这里我们通过允许url作为NSString传递来避免这个错误。
//如果url是NSString类,那么就将它转为NSURL类
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
// 防止应用程序在参数类型错误时崩溃,比如发送NSNull而不是NSURL
//如果url不是NSURL类那么就赋值为nil
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
//SDWebImageCombinedOperation表示缓存和加载器操作的组合操作。您可以使用它来取消加载过程。
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
//让上述变量的manager指向该类
operation.manager = self;
//判断该URL是否在黑名单中
BOOL isFailedUrl = NO;
//如果url存在
if (url) {
//线程等待self.failedURLsLock执行,知道其执行完毕
SD_LOCK(self.failedURLsLock);
//在self.failedURLs(失效的url名单)中查找是否有这个url
isFailedUrl = [self.failedURLs containsObject:url];
//使传入的信号量self.failedURLsLock的调用值加1,表示当前又加入一个线程等待其处理的信号量
SD_UNLOCK(self.failedURLsLock);
}
//url的绝对字符串长度为0或者(不禁用黑名单)并且Url在黑名单内,发出警告提示
//url有问题的话该函数就在这就结束了
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil";
NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL;
//调用completionBlock块,结束该函数
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] url:url];
return operation;
}
//将这个操作加入进程
SD_LOCK(self.runningOperationsLock);
[self.runningOperations addObject:operation];
SD_UNLOCK(self.runningOperationsLock);
// Preprocess the options and context arg to decide the final the result for manager
// 对操作和上下文参数进行预处理,为manager确定最终结果
//返回已处理的操作结果,包括指定图像URL的操作和context
SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
// Start the entry to load image from cache
// 启动从缓存加载图像的条目
//查询普通缓存进程
[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
//返回这个新创建的操作变量
return operation;
}
在最后两步调用了两个方法:- (SDWebImageOptionsResult *)processedResultForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context;(返回已处理的操作结果,包括指定图像URL的操作和content)和- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock;(查询普通缓存进程)这个函数中才开始真正下载此URL中的内容。
这里先将任务加入到正在执行的列表里面,然后再对context进行预处理,源代码是没有对options进行说明处理的,然后将context跟options放入result里面。context的处理源代码就不贴出来了,大概就是对SDWebImageContextImageTransformer、SDWebImageContextCacheKeyFilter、SDWebImageContextCacheSerializer这3个进行一个判断,看是否有自定义的传过来,没有就用默认的。
- (SDWebImageOptionsResult *)processedResultForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
SDWebImageOptionsResult *result;
SDWebImageMutableContext *mutableContext = [SDWebImageMutableContext dictionary];
// Image Transformer from manager
// 来自管理器的图像转换器
if (!context[SDWebImageContextImageTransformer]) {
id<SDImageTransformer> transformer = self.transformer;
[mutableContext setValue:transformer forKey:SDWebImageContextImageTransformer];
}
// Cache key filter from manager
// 从管理器缓存密钥过滤器
if (!context[SDWebImageContextCacheKeyFilter]) {
id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;
[mutableContext setValue:cacheKeyFilter forKey:SDWebImageContextCacheKeyFilter];
}
// Cache serializer from manager
// 从管理器缓存序列化器
if (!context[SDWebImageContextCacheSerializer]) {
id<SDWebImageCacheSerializer> cacheSerializer = self.cacheSerializer;
[mutableContext setValue:cacheSerializer forKey:SDWebImageContextCacheSerializer];
}
if (mutableContext.count > 0) {
if (context) {
[mutableContext addEntriesFromDictionary:context];
}
context = [mutableContext copy];
}
// Apply options processor
// 应用操作处理器
if (self.optionsProcessor) {
result = [self.optionsProcessor processedResultForURL:url options:options context:context];
}
if (!result) {
// Use default options result
// 使用默认操作结果
result = [[SDWebImageOptionsResult alloc] initWithOptions:options context:context];
}
return result;
}
这里主要是判断要到哪里去取数据,ImageCache,还是去下载,接下来就进入这个方法看一下。
这里主要是判断任务是否该走缓存查询,或者直接下载。如果是缓存查询,就进入SDImageCache里面进行缓存查询,且在此处理缓存结果的回调。否则就调用callDownloadProcessForOperation进入下一步判断。
①. 拿到imageCache,拿到缓存类型queryCacheType
②. 通过 options判断,走缓存还是下载。如果走缓存,则调用SDImageCache里面的queryImageForKey(开始进入SDImageCache的逻辑);如果走下载,则调用callDownloadProcessForOperation开始下载前的一些处理。
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Grab the image cache to use
// 获取要使用的图像缓存
id<SDImageCache> imageCache;
//判断SDWebImageContextImageCache对应内容是否符合协议SDImageCache
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
imageCache = context[SDWebImageContextImageCache];
} else {
imageCache = self.imageCache;
}
// Get the query cache type
// 获取查询缓存类型,存在的话就将其转换为integer类型数据
SDImageCacheType queryCacheType = SDImageCacheTypeAll;
if (context[SDWebImageContextQueryCacheType]) {
queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue];
}
// Check whether we should query cache
// 检查是否需要查询缓存
BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
//需要查询缓存
if (shouldQueryCache) {
//将url和context结合起来转换成一个cache的key
NSString *key = [self cacheKeyForURL:url context:context];
@weakify(operation);//定义一个弱引用self的变量,用于下面的block,避免循环引用
//开始查询
operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
@strongify(operation);//定义一个强引用self的变量
//如果operation存在并且operation被取消了
if (!operation || operation.isCancelled) {
// Image combined operation cancelled by user
// 用户取消图像组合操作,调用CompletionBlock块,做出相应提示,说明用户取消了图像组合操作
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url];
//将该operation安全移除进程,然后结束函数
[self safelyRemoveOperationFromRunning:operation];
return;
} else if (context[SDWebImageContextImageTransformer] && !cachedImage) {
//SDWebImageContextImageTransformer对应内容存在并且cachedImage不存在
// 有机会查询原始缓存而不是下载,在原始缓存中查找
// Have a chance to query original cache instead of downloading
[self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
return;
}
// Continue download process
// 继续下载过程,这里的下载不同于下面的下载,这里的下载是从缓存中下载的,而不是url中
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
}];
} else {
//不需要查询缓存
// 继续下载过程,这里的下载是直接从url中下载的
// Continue download process
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
}
}
来看看:
- (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 {
// Grab the image loader to use
// 获取要使用的图像加载器
id<SDImageLoader> imageLoader;
if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
imageLoader = context[SDWebImageContextImageLoader];
} else {
imageLoader = self.imageLoader;
}
// Check whether we should download image from network
// 检查是否需要从网络下载图像
BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
//(没有缓存图片) || (即使有缓存图片,也需要更新缓存图片)
shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
//(代理没有响应imageManager:shouldDownloadImageForURL:消息,默认返回yes,需要下载图片)|| (imageManager:shouldDownloadImageForURL:返回yes,需要下载图片)
shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
shouldDownload &= [imageLoader canRequestImageForURL:url];
//如果应该从url下载图像
if (shouldDownload) {
//存在缓存图片 && 即使有缓存图片也要下载更新图片
if (cachedImage && options & SDWebImageRefreshCached) {
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
// 如果在缓存中找到了图像,但是sdwebimagerfreshcached被提供了,通知缓存的图像
// 并尝试重新下载它,以便NSURLCache有机会从服务器刷新它。
[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
// Pass the cached image to the image loader. The image loader should check whether the remote image is equal to the cached image.
// 将缓存的图像传递给图像加载程序。图像加载程序应该检查远程图像是否等于缓存的图像。
SDWebImageMutableContext *mutableContext;
if (context) {
mutableContext = [context mutableCopy];
} else {
mutableContext = [NSMutableDictionary dictionary];
}
mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
context = [mutableContext copy];
}
@weakify(operation);
// ========== operation.loaderOperation ========== //下载操作
operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
@strongify(operation);
//如果操作不存在并且任务被取消,则什么都不做,避免和其他的completedBlock重复
if (!operation || operation.isCancelled) {
// 用户取消图像组合操作
// Image combined operation cancelled by user
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] url:url];
//存在缓存图片 && 即使有缓存图片也要下载更新图片 && 错误域相同 && error的代码为远程位置指定缓存的映像不被修改
} else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
// Image refresh hit the NSURLCache cache, do not call the completion block
// 图像刷新击中NSURLCache缓存,不调用完成块
//错误域相同 && 镜像加载操作在完成之前取消,在异步磁盘缓存查询期间,或在实际网络请求之前等待
} else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) {
// Download operation cancelled by user before sending the request, don't block failed URL
// 下载操作被用户在发送请求前取消,不要阻止失败的URL
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
//如果错误存在
} else if (error) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
//是否应该添加该url到错误名单中
BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context];
//在错误url名单中添加当前的url
if (shouldBlockFailedURL) {
SD_LOCK(self.failedURLsLock);
[self.failedURLs addObject:url];
SD_UNLOCK(self.failedURLsLock);
}
//下载成功
} else {
//如果需要下载失败后重新下载,则将当前url从失败url名单里移除
if ((options & SDWebImageRetryFailed)) {
SD_LOCK(self.failedURLsLock);
[self.failedURLs removeObject:url];
SD_UNLOCK(self.failedURLsLock);
}
// Continue store cache process
// 继续存储缓存进程
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
}
//如果完成,从当前运行的操作列表里移除当前操作
if (finished) {
[self safelyRemoveOperationFromRunning:operation];
}
}];
// ========== operation.loaderOperation ========== //
//存在缓存图片
} else if (cachedImage) {
//调用完成的block
[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
//删去当前的的下载操作(线程安全)
[self safelyRemoveOperationFromRunning:operation];
//没有缓存的图片,而且下载被代理终止了
} else {
// Image not in cache and download disallowed by delegate
// 调用完成的block
[self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
//删去当前的下载操作
[self safelyRemoveOperationFromRunning:operation];
}
}
下面来看看两个类的核心方法:
主要用来查看缓存
// ============== SDImageCache.m ============== //
@property (nonatomic, strong, readwrite, nonnull) id<SDMemoryCache> memoryCache;//内存缓存
@property (nonatomic, copy, readwrite, nonnull) NSString *diskCachePath;//磁盘缓存路径
@property (nonatomic, strong, nullable) dispatch_queue_t ioQueue;//ioQueue唯一子线程;
(nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
//key不存在,结束查找函数
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// Invalid cache type
// 无效的缓存类型,结束查找函数
if (queryCacheType == SDImageCacheTypeNone) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// First check the in-memory cache...
//================查看内存的缓存=================//
UIImage *image;
//如果查询缓存类型不是只处理磁盘缓存,
if (queryCacheType != SDImageCacheTypeDisk) {
//在内存中找
image = [self imageFromMemoryCacheForKey:key];
}
// 如果存在,直接调用block,将image,data,CaheType传进去
if (image) {
//如果操作为解码获取第一帧产生静态图像
if (options & SDImageCacheDecodeFirstFrameOnly) {
// Ensure static image
// 确保静态图像
Class animatedImageClass = image.class;
//如果图像是动画 || animatedImageClass是UIImage的实例 && animatedImageClass遵循SDAnimatedImage协议
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 = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
//因为图片有缓存可供使用,所以不用实例化NSOperation,直接返回nil
return nil;
}
// Second check the disk cache...
//================查看磁盘的缓存=================//
NSOperation *operation = [NSOperation new];
// Check whether we need to synchronously query disk
// 1. in-memory cache hit & memoryDataSync
// 2. in-memory cache miss & diskDataSync
// 检查是否需要同步查询磁盘
//(在内存缓存命中) && memoryDataSync
//(内存缓存错过) && diskDataSync
BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
(!image && options & SDImageCacheQueryDiskDataSync));
void(^queryDiskBlock)(void) = ^{
// 在用之前就判断operation是否被取消了,作者考虑的非常严谨,如果取消了就直接结束该函数了
if (operation.isCancelled) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return;
}
@autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
if (image) {
// 图像来自内存缓存,但需要图像数据
// the image is from in-memory cache, but need image data
diskImage = image;
} else if (diskData) {
//应该缓存到内存
BOOL shouldCacheToMomery = YES;
if (context[SDWebImageContextStoreCacheType]) {
SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
}
// 解码图像数据,只有在内存缓存错过
// decode image data only if in-memory cache missed
diskImage = [self diskImageForKey:key data:diskData options:options context:context];
if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) {
// cost 被用来计算缓存中所有对象的代价。当内存受限或者所有缓存对象的总代价超过了最大允许的值时,缓存会移除其中的一些对象。
NSUInteger cost = diskImage.sd_memoryCost;
//存入内存缓存中
[self.memoryCache setObject:diskImage forKey:key cost:cost];
}
}
if (doneBlock) {
if (shouldQueryDiskSync) {
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
}
};
// Query in ioQueue to keep IO-safe
// 在唯一的子线程:self.ioQueue中查询,保证io安全
//应该同步查询磁盘
if (shouldQueryDiskSync) {
//线程等待
dispatch_sync(self.ioQueue, queryDiskBlock);
} else {
//线程不等待
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation;
}
核心方法:
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
// URL将被用作回调字典的键,所以它不能为nil。如果为nil,立即调用没有图像或数据的已完成块。
if (url == nil) {
//如果completedBlock存在但是因为没有url所以就没办法访问,就直接提示错误就行
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
completedBlock(nil, nil, error, YES);
}
return nil;
}
SD_LOCK(self.operationsLock);
//定义一个下载操作取消令牌
id downloadOperationCancelToken;
//当前下载操作中取出SDWebImageDownloaderOperation实例
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
// There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`.
// 有一种情况是,操作可能被标记为完成或取消,但没有从'self.URLOperations'中删除。
//操作不存在||操作已完成||操作被取消
if (!operation || operation.isFinished || operation.isCancelled) {
//创建一个下载操作给operation
//operation中保存了progressBlock和completedBlock
operation = [self createDownloaderOperationWithUrl:url options:options context:context];
//如果操作不存在,即上述创建失败,返回一个错误信息
if (!operation) {
SD_UNLOCK(self.operationsLock);
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
completedBlock(nil, nil, error, YES);
}
return nil;
}
@weakify(self);
//如果创建成功了,我们再继续完善其completionBlock代码块
operation.completionBlock = ^{
@strongify(self);
if (!self) {
return;
}
//在操作数组中删除此url的操作
SD_LOCK(self.operationsLock);
[self.URLOperations removeObjectForKey:url];
SD_UNLOCK(self.operationsLock);
};
//将该url的下载操作赋值给self.URLOperations操作数组
self.URLOperations[url] = operation;
// Add the handlers before submitting to operation queue, avoid the race condition that operation finished before setting handlers.
// 在提交到操作队列之前添加处理程序,避免操作在设置处理程序之前完成的竞态条件。
//下载操作取消令牌
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
// Add operation to operation queue only after all configuration done according to Apple's doc.
// `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
// 根据苹果文档完成所有配置后,才能将操作添加到操作队列中。
// 'addOperation:'不会同步执行'operation.completionBlock',因此不会导致死锁。
//将此下载操作添加到下载队列
[self.downloadQueue addOperation:operation];
} else {
// When we reuse the download operation to attach more callbacks, there may be thread safe issue because the getter of callbacks may in another queue (decoding queue or delegate queue)
// So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.
// 当我们重用下载操作来附加更多的回调时,可能会有线程安全问题,因为回调的getter可能在另一个队列(解码队列或委托队列)
// 所以我们锁定了这里的操作,在'SDWebImageDownloaderOperation'中,我们使用' @synchronized(self)'来确保这两个类之间的线程安全。
@synchronized (operation) {
//下载操作取消令牌
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
}
//如果该操作没有在执行
if (!operation.isExecuting) {
//给该操作赋相应的优先级操作
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
} else {
operation.queuePriority = NSOperationQueuePriorityNormal;
}
}
}
SD_UNLOCK(self.operationsLock);
//创建该下载的token,这里 downloadOperationCancelToken 默认是一个字典,存放 progressBlock 和 completedBlock
//使用operation初始化该下载操作token
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url; //保存url
token.request = operation.request; //保存请求
token.downloadOperationCancelToken = downloadOperationCancelToken; //保存下载操作取消令牌
return token;
}
我们发现这个方法在之前的方法里并没有调用过,我们在全局搜索下这个函数在哪用过呢?
在这个函数里:
这个方法是一个工具,用来生成SDWebImageDownloaderOptions对象,我们向前找发现在SDWebImageManager的callDownloadProcessForOperation方法里调了这个:
- (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDImageLoaderCompletedBlock)completedBlock {
UIImage *cachedImage = context[SDWebImageContextLoaderCachedImage];
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveLoad) downloaderOptions |= SDWebImageDownloaderProgressiveLoad;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
if (options & SDWebImageAvoidDecodeImage) downloaderOptions |= SDWebImageDownloaderAvoidDecodeImage;
if (options & SDWebImageDecodeFirstFrameOnly) downloaderOptions |= SDWebImageDownloaderDecodeFirstFrameOnly;
if (options & SDWebImagePreloadAllFrames) downloaderOptions |= SDWebImageDownloaderPreloadAllFrames;
if (options & SDWebImageMatchAnimatedImageClass) downloaderOptions |= SDWebImageDownloaderMatchAnimatedImageClass;
if (cachedImage && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveLoad;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
return [self downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:completedBlock];
}
然后我们再看downloadImageWithURL里用到的的方法,这个才是真正的下载:
通过这个函数createDownloaderOperationWithUrl赋值给了operation然后通过operation才赋值给了token
- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context {
//创建一个等待时间
NSTimeInterval timeoutInterval = self.config.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
// 为了防止潜在的重复缓存(NSURLCache + SDImageCache),我们禁用图像请求的缓存
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
//创建下载请求
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
mutableRequest.HTTPShouldHandleCookies = SD_OPTIONS_CONTAINS(options, SDWebImageDownloaderHandleCookies);
mutableRequest.HTTPShouldUsePipelining = YES;
//线程安全的创建一个请求头
SD_LOCK(self.HTTPHeadersLock);
mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
SD_UNLOCK(self.HTTPHeadersLock);
// Context Option
// Context选项
SDWebImageMutableContext *mutableContext;
if (context) {
mutableContext = [context mutableCopy];
} else {
mutableContext = [NSMutableDictionary dictionary];
}
// Request Modifier
//请求修饰符,设置请求修饰符,在图像加载之前修改原始的下载请求。返回nil将取消下载请求。
id<SDWebImageDownloaderRequestModifier> requestModifier;
if ([context valueForKey:SDWebImageContextDownloadRequestModifier]) {
requestModifier = [context valueForKey:SDWebImageContextDownloadRequestModifier];
} else {
//self.requestModifier默认为nil,表示不修改原始下载请求。
requestModifier = self.requestModifier;
}
NSURLRequest *request;
//如果请求修饰符存在
if (requestModifier) {
NSURLRequest *modifiedRequest = [requestModifier modifiedRequestWithRequest:[mutableRequest copy]];
// If modified request is nil, early return
// 如果修改请求为nil,则提前返回
if (!modifiedRequest) {
return nil;
} else {
request = [modifiedRequest copy];
}
} else {
request = [mutableRequest copy];
}
// Response Modifier
// 响应修饰符,设置响应修饰符来修改图像加载期间的原始下载响应。返回nil将标志当前下载已取消。
id<SDWebImageDownloaderResponseModifier> responseModifier;
if ([context valueForKey:SDWebImageContextDownloadResponseModifier]) {
responseModifier = [context valueForKey:SDWebImageContextDownloadResponseModifier];
} else {
//self.responseModifier默认为nil,表示不修改原始下载响应。
responseModifier = self.responseModifier;
}
//如果响应修饰存在
if (responseModifier) {
mutableContext[SDWebImageContextDownloadResponseModifier] = responseModifier;
}
// Decryptor
// 图像解码,设置解密器对原始下载数据进行解密后再进行图像解码。返回nil将标志下载失败。
id<SDWebImageDownloaderDecryptor> decryptor;
if ([context valueForKey:SDWebImageContextDownloadDecryptor]) {
decryptor = [context valueForKey:SDWebImageContextDownloadDecryptor];
} else {
//self.decryptor默认为nil,表示不修改原始下载数据。
decryptor = self.decryptor;
}
//如果图像解码操作存在
if (decryptor) {
mutableContext[SDWebImageContextDownloadDecryptor] = decryptor;
}
context = [mutableContext copy];
// Operation Class
// 操作类
Class operationClass = self.config.operationClass;
//操作类存在 && 操作类是NSOperation的实例类 && 操作类遵守SDWebImageDownloaderOperation协议
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) {
// Custom operation class
// 自定义操作类(可以自行修改和定义)
} else {
//默认操作类
operationClass = [SDWebImageDownloaderOperation class];
}
//创建下载操作:SDWebImageDownloaderOperation用于请求网络资源的操作,它是一个 NSOperation 的子类
NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];
//如果operation实现了setCredential:方法
if ([operation respondsToSelector:@selector(setCredential:)]) {
//url证书
if (self.config.urlCredential) {
operation.credential = self.config.urlCredential;
} else if (self.config.username && self.config.password) {
operation.credential = [NSURLCredential credentialWithUser:self.config.username password:self.config.password persistence:NSURLCredentialPersistenceForSession];
}
}
//如果operation实现了setMinimumProgressInterval:方法
if ([operation respondsToSelector:@selector(setMinimumProgressInterval:)]) {
operation.minimumProgressInterval = MIN(MAX(self.config.minimumProgressInterval, 0), 1);
}
//设置该url的操作优先级
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
if (self.config.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically, each previous adding operation can dependency the new operation
// This can gurantee the new operation to be execulated firstly, even if when some operations finished, meanwhile you appending new operations
// Just make last added operation dependents new operation can not solve this problem. See test case #test15DownloaderLIFOExecutionOrder
// 通过系统地模拟后进先出的执行顺序,前一个添加的操作可以依赖于新操作
// 这样可以保证先执行新操作,即使有些操作完成了,同时又追加了新操作
// 仅仅使上次添加的操作依赖于新的操作并不能解决这个问题。参见测试用例#test15DownloaderLIFOExecutionOrder
for (NSOperation *pendingOperation in self.downloadQueue.operations) {
[pendingOperation addDependency:operation];
}
}
return operation;
}
SDWebImage总共嵌套了七八层类去完成操作,比之前学的JSONModel复杂很多,我看得还比较浅,在这补充下一些类的作用: