一、基础框架

使用这个框架时候,只需要提供一个下载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+WebCacheUIButton+WebCache 为下载图片的操作提供接口内部SDWebImageManger负责处理和协调 SDWebImageDownloaderSDWebImageCacheSDWebImageDownloader负责具体的下载任务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 &amp;&amp; finished &amp;&amp; !error &amp;&amp; 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同时管理SDImageCacheSDWebImageDownloader两个类,它是这一层的老大哥。在下载任务开始的时候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的回调处理,我们分别看一下SDImageCacheSDWebImageDownloader内部具体是如何工作的。首先看一下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;
}

如果在参数里添加了nonnull关键字,那么编译器就可以检查传入的参数是否为nil,如果是,则编译器会有警告

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进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注