一、基础框架
在使用这个框架的时候,只需要提供一个下载的url和占位图就可以在回调里拿到下载后的图片:
[imageview sd_setImageWithURL:[NSURL URLWithString:@"pic.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder"] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
imageview.image = image;
NSLog(@"图片加载完成");
}];
而且我们还可以不设置占位图片,也可以不使用回调的block,主要是有下载的URL就行,这个非常的灵活:
//图片下载完成后直接显示下载后的图片
[imageview sd_setImageWithURL:[NSURL URLWithString:@"pic.jpg"]];
在最开始先简单介绍这个框架:
这个框架的核心类是SDWebImageManger
,在外部有UIImageView+WebCache
和 UIButton+WebCache
为下载图片的操作提供接口。内部有SDWebImageManger
负责处理和协调 SDWebImageDownloader
和 SDWebImageCache
:SDWebImageDownloader
负责具体的下载任务,SDWebImageCache
负责关于缓存的工作:添加,删除,查询缓存。
浅看一下SDWebImage框架的调用流程图:
在说明之前,浅看一下SDWebImage
的基本流程。
从这个流程图里可以大致看出,该框架分为两个层:UIKit层(负责接收下载参数) 和 工具层(负责下载操作和缓存)。
二、UIKit层
该框架最外层的类是UIImageView+WebCache
,我们将图片的URL,占位图片直接给这个类。下面是这个类的公共接口:
// ============== UIImageView + WebCache.h ============== //
- (void)sd_setImageWithURL:(nullable NSURL *)url;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options;
- (void)sd_setImageWithURL:(nullable NSURL *)url
completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
可以看出,这个类提供的接口非常灵活,可以根据我们自己的需求来调用其中某一个方法,而这些方法到最后都会走到:
// ============== UIImageView + WebCache.m ============== //
- (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;
/**
* 使用“url”、占位符、自定义选项和上下文设置imageView“image”。
*
* 下载是异步和缓存的。
*
* @param url 图像的url。
* @param placeholder 最初要设置的图像,直到图像请求完成。
* @param options 下载图像时要使用的选项。@有关可能的值,请参见SDWebImageOptions。
* @param context 上下文包含用于执行指定更改或过程的不同选项,请参见“SDWebImageContextOption”。这将保存“选项”枚举无法保存的额外对象。
* @param progressBlock 下载图像时调用的块。
* @注意,该进度块是在后台队列上执行的。
* @param completedBlock 操作完成时调用的块。此块没有返回值,将请求的UIImage作为第一个参数。
* 如果出现错误,图像参数为零,第二个参数可能包含NSError。
* 第三个参数是一个布尔值,指示是从本地缓存还是从网络检索图像。
* 第四个参数是原始图像url。
*/
而这个方法里面,调用的是UIView+WebCache
分类的:
// ============== UIView + WebCache.m ============== //
- (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;
/**
* 使用“url”和占位符图像设置imageView“image”。
*
* 下载是异步和缓存的。
*
* @param url 图像的url。
* @param placeholder 最初要设置的图像,直到图像请求完成。
* @param options 下载图像时要使用的选项。@有关可能的值,请参见SDWebImageOptions。
* @param context 上下文包含用于执行指定更改或过程的不同选项,请参见“SDWebImageContextOption”。这将保存“选项”枚举无法保存的额外对象。
* @param setImageBlock 块用于自定义设置图像代码。如果未提供,请使用内置的设置图像代码(当前支持'UIImageView/NSImageView'和'UIButton/NSButton')
* @param progressBlock 下载图像时调用的块。
* @注意进度块是在后台队列上执行的。
* @param completedBlock 当操作完成时调用的块。
* 这个块没有返回值,并以请求的UIImage作为第一个参数,NSData表示作为第二个参数。
* 如果发生错误,image参数为nil,第三个参数可能包含一个NSError。
*
* 第四个参数是一个“SDImageCacheType”enum,表示图像是从本地缓存、内存缓存还是网络检索到的。
*
* 第五个参数通常总是“YES”。但是,如果你提供SDWebImageAvoidAutoSetImage与SDWebImageProgressiveLoad选项,以启用渐进下载和设置自己的映像。因此,对部分图像重复调用该块。当图像完全下载后,最后一次调用该块,并将最后一个参数设置为YES。
*
* 最后一个参数是原始图像URL。
*/
为什么不是
UIImageView+WebCache
而要上一层到UIView
的分类里呢? 因为SDWebImage
框架也得支持UIButton
的下载图片等方法,所以需要在它们的父类:UIView
里面统一一个下载方法。
UIKit层的基层函数:
// ============== UIView + WebCache.m ============== //
- (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) {
// 复制以避免可变对象
context = [context copy];
} else {
context = [NSDictionary dictionary];
}
//获取一个有效的操作的对应键值,这里获取的是"设置图片操作的键"
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
//SDWebImageContextOption const SDWebImageContextSetImageOperationKey = @"setImageOperationKey";
//如果传递的context中没有该图片操作键,就新建一个该类的操作键赋给context
if (!validOperationKey) {
// 将操作键传递给下游,可用于跟踪操作或图像视图类
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];
});
}
//-------------------------url存在---------------------------//
if (url) {
//重置进程
NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
if (imageProgress) {
imageProgress.totalUnitCount = 0;
imageProgress.completedUnitCount = 0;
}
#if SD_UIKIT || SD_MAC //环境判断
// 检查并启动图像指示灯
[self sd_startImageIndicator]; //开启图像指示器
//self.sd_imageIndicator
//图像加载期间的图像指示器。如果你不需要指示器,请指定nil。默认为零
//这个设置将移除旧的指示器视图,并将新的指示器视图添加到当前视图的子视图中。
//@注意,因为这是UI相关的,你应该只从主队列访问。
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif//环境判断
//从context字典中获取该键对应的数据
//SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager";
SDWebImageManager *manager = context[SDWebImageContextCustomManager];
//如果不存在,就新创建一个单例
if (!manager) {
manager = [SDWebImageManager sharedManager];
} else {
// 删除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); //@weakify(self) = @autoreleasepool{} __weak __typeof__ (self) self_weak_ = self;
//----------operation----------//
//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); //@strongify(self) = @autoreleasepool{} __strong __typeof__(self) self = self_weak_;
//如果不存在就直接,结束函数了。
if (!self) { return; }
// 如果进度没有更新,将其标记为完成状态
if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
#if SD_UIKIT || SD_MAC
// 检查和停止图像指示灯,如果完成了就停止
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:我们得到了一个图像,但是SDWebImageAvoidAutoSetImage标志被设置了
//或
// case 1b:我们没有得到图像,SDWebImageDelayPlaceholder没有设置
if (shouldNotSetImage) { //如果应该不设置图像
//主线程不等待安全的调用上述创建的callCompletedBlockClojure
dispatch_main_async_safe(callCompletedBlockClojure);
return; //结束函数
}
UIImage *targetImage = nil; //目标图像
NSData *targetData = nil; //目标数据
if (image) {
// case 2a: 我们得到一个图像,SDWebImageAvoidAutoSetImage没有设置
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: 我们没有图像,并且设置了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 {
// 从磁盘(并且,用户不使用同步查询)
if (cacheType == SDImageCacheTypeMemory) { //内存
shouldUseTransition = NO;
} else if (cacheType == SDImageCacheTypeDisk) { //磁盘
if (options & SDWebImageQueryMemoryDataSync || options & SDWebImageQueryDiskDataSync) {
shouldUseTransition = NO;
} else {
shouldUseTransition = YES;
}
} else {
// 缓存类型无效,请回退
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不存在---------------------------//
}
存储进程的特殊字典:
这里url
存在的最后一行代码[self sd_setImageLoadOperation:operation forKey:validOperationKey];
,使用一个字典operationDictionary
专门用作存储操作的缓存,随时添加,删除操作任务。而这个字典是UIView+WebCacheOperation
分类的关联对象,它通过sd_operationDictionary
存取方法来获取该字典:
// ============== UIView + WebCacheOperation.m ============== //
//设置图像加载操作,使用一个字典存储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];
}
}
}
}
// ============== UIView + WebCacheOperation.m ============== //
//获取关联对象:operations(用来存放操作的字典)
- (SDOperationsDictionary *)sd_operationDictionary {
//创建一个互斥锁,保证此时没有其它线程对self对象进行修改,然后再获取这个字典
@synchronized(self) {
//获取存放操作的字典,objc_getAssociatedObject获取关联
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
//如果存在就直接返回
if (operations) {
return operations;
}
//如果没有,就新建一个
operations = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
//objc_setAssociatedObject创建关联
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
}
为什么不直接在
UIImageView+WebCache
里直接关联这个对象呢?
这里专门创造一个分类UIView+WebCacheOperation
来管理操作缓存(字典),这样即美化代码还能方便操作,因为这个字典的东西都是通过这个类来进行操作的,那么我们需要更改其代码或操作直接在这里进行更改就行了,而且还可以直接对这个类进行关联对象的设置。
到这里,UIKit层
上面的东西就差不多了,下面浅讲一下工具层
。
三、工具层
上文提到过,SDWebImageManager
同时管理SDImageCache
和SDWebImageDownloader
两个类,它是这一层的老大哥。在下载任务开始的时候,SDWebImageManager
首先访问SDImageCache
来查询是否存在缓存,如果有缓存,直接返回缓存的图片。如果没有缓存,就命令SDWebImageDownloader
来下载图片,下载成功后,存入缓存,显示图片。以上是SDWebImageManager
大致的工作流程。
manager类的关键属性:
在详细讲解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; //记录当前正在执行的操作
从url中下载图像的方法:
SDWebImageManager
下载图片的方法只有一个:
- (nullable SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock;
/**
* 如果缓存中没有图像,则在给定URL下载图像,否则返回缓存版本。
*
* @param url 图像的URL
* @param options 指定此请求使用的选项的掩码
* @param context 上下文包含不同的选项来执行指定的更改或流程,参见' SDWebImageContextOption '。这将保存'options'枚举不能保存的额外对象。
* @param progressBlock 下载图像时调用的块
* @注意 进度块在后台队列上执行
* @param completedBlock 当操作完成时调用的块
* 必选参数。
* 这个块没有返回值,并以请求的UIImage作为第一个参数,NSData表示作为第二个参数。
* 如果发生错误,image参数为nil,第三个参数可能包含一个NSError。
* 第四个参数是一个“SDImageCacheType”枚举,指示是否从本地缓存检索到图像或者从内存缓存或者网络。
* 当使用SDWebImageProgressiveLoad选项并下载镜像时,第五个参数设置为NO。因此,对部分图像重复调用该块。当图像完全下载后,最后一次调用该块,并将最后一个参数设置为YES。
* 最后一个参数是原始图像URL
*
* @return 返回一个SDWebImageCombinedOperation的实例,你可以取消加载过程。
*/
看一下这个方法的具体实现:
// ============== SDWebImageManager.m ============== //
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock {
// 没有completedBlock调用这个方法是没有意义的
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// 很常见的错误是使用NSString对象而不是NSURL发送URL。由于一些奇怪的原因,Xcode不会对这种类型不匹配抛出任何警告。这里我们通过允许url作为NSString传递来避免这个错误。
//如果url是NSString类,那么就将它转为NSURL类
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// 防止应用程序在参数类型错误时崩溃,比如发送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);
// 对操作和上下文参数进行预处理,为manager确定最终结果
//返回已处理的操作结果,包括指定图像URL的操作和context
SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
// 启动从缓存加载图像的条目
//查询普通缓存进程
[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中的内容。
返回OptionsResult的函数:
// ============== SDWebImageManager.m - Helper ============== //
- (SDWebImageOptionsResult *)processedResultForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
SDWebImageOptionsResult *result;
SDWebImageMutableContext *mutableContext = [SDWebImageMutableContext dictionary];
// 来自管理器的图像转换器
if (!context[SDWebImageContextImageTransformer]) {
id<SDImageTransformer> transformer = self.transformer;
[mutableContext setValue:transformer forKey:SDWebImageContextImageTransformer];
}
// 从管理器缓存密钥过滤器
if (!context[SDWebImageContextCacheKeyFilter]) {
id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;
[mutableContext setValue:cacheKeyFilter forKey:SDWebImageContextCacheKeyFilter];
}
// 从管理器缓存序列化器
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];
}
// 应用操作处理器
if (self.optionsProcessor) {
result = [self.optionsProcessor processedResultForURL:url options:options context:context];
}
if (!result) {
// 使用默认操作结果
result = [[SDWebImageOptionsResult alloc] initWithOptions:options context:context];
}
return result;
}
查询普通缓存进程函数:
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// 获取要使用的图像缓存
id<SDImageCache> imageCache;
//判断SDWebImageContextImageCache对应内容是否符合协议SDImageCache
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
imageCache = context[SDWebImageContextImageCache];
} else {
imageCache = self.imageCache;
}
// 获取查询缓存类型,存在的话就将其转换为integer类型数据
SDImageCacheType queryCacheType = SDImageCacheTypeAll;
if (context[SDWebImageContextQueryCacheType]) {
queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue];
}
// 检查是否需要查询缓存
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) {
// 用户取消图像组合操作,调用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不存在
// 有机会查询原始缓存而不是下载,在原始缓存中查找
[self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
return;
}
// 继续下载过程,这里的下载不同于下面的下载,这里的下载是从缓存中下载的,而不是url中
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
}];
} else { //不需要查询缓存
// 继续下载过程,这里的下载是直接从url中下载的
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
}
}
上述的函数只是对缓存进行了查找,程序现在还没有真正的开始从url下载,而其最后调用的两个函数- (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
才是下载的操作,通过上述传入的不同参数来判断是到底是从url中下载还是从缓存中下载。
调用下载图像操作:
- (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 {
// 获取要使用的图像加载器
id<SDImageLoader> imageLoader;
if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
imageLoader = context[SDWebImageContextImageLoader];
} else {
imageLoader = self.imageLoader;
}
// 检查是否需要从网络下载图像
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) {
// 如果在缓存中找到了图像,但是sdwebimagerfreshcached被提供了,通知缓存的图像
// 并尝试重新下载它,以便NSURLCache有机会从服务器刷新它。
[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
// 将缓存的图像传递给图像加载程序。图像加载程序应该检查远程图像是否等于缓存的图像。
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) {
// 用户取消图像组合操作
[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) {
// 图像刷新击中NSURLCache缓存,不调用完成块
//错误域相同 && 镜像加载操作在完成之前取消,在异步磁盘缓存查询期间,或在实际网络请求之前等待
} else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) {
// 下载操作被用户在发送请求前取消,不要阻止失败的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);
}
// 继续存储缓存进程
[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 {
// 调用完成的block
[self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
//删去当前的下载操作
[self safelyRemoveOperationFromRunning:operation];
}
}
看完了SDWebImageManager
的回调处理,我们分别看一下SDImageCache
和SDWebImageDownloader
内部具体是如何工作的。首先看一下SDImageCache
:
SDImageCache
属性:
// ============== 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唯一子线程;
核心方法:查询缓存
// ============== SDImageCache.m ============== //
- (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;
}
// 无效的缓存类型,结束查找函数
if (queryCacheType == SDImageCacheTypeNone) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
//================查看内存的缓存=================//
UIImage *image;
//如果查询缓存类型不是只处理磁盘缓存,
if (queryCacheType != SDImageCacheTypeDisk) {
//在内存中找
image = [self imageFromMemoryCacheForKey:key];
}
// 如果存在,直接调用block,将image,data,CaheType传进去
if (image) {
//如果操作为解码获取第一帧产生静态图像
if (options & SDImageCacheDecodeFirstFrameOnly) {
// 确保静态图像
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) {
// 检查图像类匹配
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;
}
//================查看磁盘的缓存=================//
NSOperation *operation = [NSOperation new];
// 检查是否需要同步查询磁盘
//(在内存缓存命中) && 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) {
// 图像来自内存缓存,但需要图像数据
diskImage = image;
} else if (diskData) {
//应该缓存到内存
BOOL shouldCacheToMomery = YES;
if (context[SDWebImageContextStoreCacheType]) {
SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
}
// 解码图像数据,只有在内存缓存错过
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);
});
}
}
}
};
// 在唯一的子线程:self.ioQueue中查询,保证io安全
//应该同步查询磁盘
if (shouldQueryDiskSync) {
//线程等待
dispatch_sync(self.ioQueue, queryDiskBlock);
} else {
//线程不等待
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation;
}
SDWebImageDownloader
属性
// ============== SDWebImageDownloader.m ============== //
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;//下载队列
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, NSOperation<SDWebImageDownloaderOperation> *> *URLOperations;//操作数组
@property (strong, nonatomic, nullable) NSMutableDictionary<NSString *, NSString *> *HTTPHeaders;//HTTP请求头
@property (strong, nonatomic, nonnull) dispatch_semaphore_t HTTPHeadersLock;//一个锁来保持对'HTTPHeaders'的访问是线程安全的
@property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock;//一个锁来保持对'URLOperations'的访问是线程安全的
@property (strong, nonatomic) NSURLSession *session;//运行数据任务的会话
创建一个下载token
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// 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];
// 有一种情况是,操作可能被标记为完成或取消,但没有从'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;
// 在提交到操作队列之前添加处理程序,避免操作在设置处理程序之前完成的竞态条件。
//下载操作取消令牌
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
// 根据苹果文档完成所有配置后,才能将操作添加到操作队列中。
// 'addOperation:'不会同步执行'operation.completionBlock',因此不会导致死锁。
//将此下载操作添加到下载队列
[self.downloadQueue addOperation:operation];
} else {
// 当我们重用下载操作来附加更多的回调时,可能会有线程安全问题,因为回调的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];
//保存url
token.url = url;
//保存请求
token.request = operation.request;
//保存下载操作取消令牌
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}
我们看到这个方法好像并没有什么网络请求的操作,因为其中将请求的操作使用- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url options:(SDWebImageDownloaderOptions)options context:(nullable SDWebImageContext *)context
函数赋值给了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;
}
// 为了防止潜在的重复缓存(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选项
SDWebImageMutableContext *mutableContext;
if (context) {
mutableContext = [context mutableCopy];
} else {
mutableContext = [NSMutableDictionary dictionary];
}
//请求修饰符,设置请求修饰符,在图像加载之前修改原始的下载请求。返回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]];
// 如果修改请求为nil,则提前返回
if (!modifiedRequest) {
return nil;
} else {
request = [modifiedRequest copy];
}
} else {
request = [mutableRequest copy];
}
// 响应修饰符,设置响应修饰符来修改图像加载期间的原始下载响应。返回nil将标志当前下载已取消。
id<SDWebImageDownloaderResponseModifier> responseModifier;
if ([context valueForKey:SDWebImageContextDownloadResponseModifier]) {
responseModifier = [context valueForKey:SDWebImageContextDownloadResponseModifier];
} else {
//self.responseModifier默认为nil,表示不修改原始下载响应。
responseModifier = self.responseModifier;
}
//如果响应修饰存在
if (responseModifier) {
mutableContext[SDWebImageContextDownloadResponseModifier] = responseModifier;
}
// 图像解码,设置解密器对原始下载数据进行解密后再进行图像解码。返回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];
// 操作类
Class operationClass = self.config.operationClass;
//操作类存在 && 操作类是NSOperation的实例类 && 操作类遵守SDWebImageDownloaderOperation协议
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) {
// 自定义操作类(可以自行修改和定义)
} 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) {
// 通过系统地模拟后进先出的执行顺序,前一个添加的操作可以依赖于新操作
// 这样可以保证先执行新操作,即使有些操作完成了,同时又追加了新操作
// 仅仅使上次添加的操作依赖于新的操作并不能解决这个问题。参见测试用例#test15DownloaderLIFOExecutionOrder
for (NSOperation *pendingOperation in self.downloadQueue.operations) {
[pendingOperation addDependency:operation];
}
}
return operation;
}
到这里SDWebImage
的核心方法都讲解完毕了,其他没有讲到的部分以后会慢慢添加上去。
四、零碎的知识点
1.运行时存取关联对象:
存关联对象:
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//将operations对象关联给self,地址为&loadOperationKey,语义是OBJC_ASSOCIATION_RETAIN_NONATOMIC。
取关联对象:
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
//将operations对象通过地址&loadOperationKey从self里取出来
2. 数组的写操作需要加锁(多线程访问,避免覆写):
//给self.runningOperations加锁
//self.runningOperations数组的添加操作
@synchronized(self.runningOperations) {
[self.runningOperations addObject:operation];
}
//self.runningOperations数组的删除操作
- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
@synchronized(self.runningOperations) {
if (operation) {
[self.runningOperations removeObject:operation];
}
}
}
3. 确保在主线程的宏:
dispatch_main_async_safe(^{
//将下面这段代码放在主线程中
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
//宏定义:
#define dispatch_main_async_safe(block)
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
#endif
4. 设置不能为nil的参数:
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
if ((self = [super init])) {
_imageCache = cache;
_imageDownloader = downloader;
_failedURLs = [NSMutableSet new];
_runningOperations = [NSMutableArray new];
}
return self;
}
5. 容错,强制转换类型:
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
在传入的参数为
NSString
时(但是方法参数要求是NSURL
),自动转换为NSURL
。
以上内容都是浅看一下,没有没有更深的理解了,以后再慢慢补充。
原文地址:https://blog.csdn.net/m0_55124878/article/details/124433357
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_29282.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!