SDWebImage是一个开源的第三方库,它供给了UIImageView的一个分类,以支撑从长途服务器下载并缓存图片的功用。它具有以下功用:

  1. 一个异步的图片加载器。
  2. 一个异步的内存+磁盘图片缓存
  3. 支撑GIF、WebP图片
  4. 后台图片解压缩处理
  5. 保证同一个URL的图片不被屡次下载
  6. 保证不合法的URL不会被重复加载
  7. 保证下载及缓存时,主线程不被阻塞。

大致流程如下:

[iOS开发]SDWebImage源码学习

调用

接下来一点点看,咱们一般都会运用办法加载图片:

//UIImage+WebCache.h
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                 completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;

UIImage+WebCache.h文件里,咱们还能够找到常见的:

- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

等常用办法。 关于SDExternalCompletionBlock,咱们能够在SDWebImageManager.h中找到界说:

typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL);

查看UIImage+WebCache.m文件,能够看到这些办法的完结代码:

- (void)sd_setImageWithURL:(nullable NSURL *)url {
    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:options context:context progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock {
    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:options context:nil progress:progressBlock completed:completedBlock];
}

它们终究都调用了

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                   context:(nullable SDWebImageContext *)context
                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

办法。查看它的代码:

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                   context:(nullable SDWebImageContext *)context
                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock {
    [self sd_internalSetImageWithURL:url
                    placeholderImage:placeholder
                             options:options
                             context:context
                       setImageBlock:nil
                            progress:progressBlock
                           completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
                               if (completedBlock) {
                                   completedBlock(image, error, cacheType, imageURL);
                               }
                           }];
}

发现它调用了

- (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;

办法。其扼要流程为:

  1. SDWebImageContext 复制并转化为 immutable,获取其间的 validOperationKey 值作为校验 id,默许值为当时 view 的类名;
  2. 履行 sd_cancelImageLoadOperationWithKey 撤销上一次使命,保证没有当时正在进行的异步下载操作, 不会与即将进行的操作发生冲突;
  3. 设置占位图;
  4. 初始化 SDWebImageManagerSDImageLoaderProgressBlock , 重置 NSProgressSDWebImageIndicator;
  5. 敞开下载loadImageWithURL: 并将回来的 SDWebImageOperation 存入 sd_operationDictionarykeyvalidOperationKey;
  6. 取到图片后,调用 sd_setImage: 一同为新的 image 增加 Transition 过渡动画;
  7. 动画完毕后中止 indicator

UIView+Cache.h文件中能够找到它的完结:

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                           context:(nullable SDWebImageContext *)context
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDImageLoaderProgressBlock)progressBlock
                         completed:(nullable SDInternalCompletionBlock)completedBlock {
    if (context) {
        // copy to avoid mutable object
        // 复制以避免可变目标
        context = [context copy];
    } else {
        context = [NSDictionary dictionary];
    }
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
    if (!validOperationKey) {
        // pass through the operation key to downstream, which can used for tracing operation or image view class
        // 经过操作键传递到下游,可用于盯梢操作或图画视图类
        validOperationKey = NSStringFromClass([self class]);
        SDWebImageMutableContext *mutableContext = [context mutableCopy];
        mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
        context = [mutableContext copy];
    }
    self.sd_latestOperationKey = validOperationKey;
    //1.撤销从前的下载使命
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    self.sd_imageURL = url;
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
        	//设置占位图
        	// 作为图片下载完结之前的替代图片。
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        });
    }
    if (url) {
        // reset the progress
        // 重置进展
        NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
        // 获取图画加载进展
        if (imageProgress) {
            imageProgress.totalUnitCount = 0;
            imageProgress.completedUnitCount = 0;
        }
#if SD_UIKIT || SD_MAC
        // check and start image indicator
        // 是否显现进展条
        [self sd_startImageIndicator];
        id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
		// 获取图画办理者目标
        SDWebImageManager *manager = context[SDWebImageContextCustomManager];
        // 假如自界说了图画办理者目标就用自界说的,不然就用单例目标
        if (!manager) {
            manager = [SDWebImageManager sharedManager];
        } else {
            // remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager)
            // 删去此办理器以避免保存周期(办理器 -> 加载程序 -> 操作 -> 上下文 -> 办理器)
            SDWebImageMutableContext *mutableContext = [context mutableCopy];
            mutableContext[SDWebImageContextCustomManager] = nil;
            context = [mutableContext copy];
        }
        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);
            }
        };
        @weakify(self);
        id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            @strongify(self);
            if (!self) { return; }
            // if the progress not been updated, mark it to complete state
            // 假如进展未更新,请将其符号为完结状况
            if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
                imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
                imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
            }
#if SD_UIKIT || SD_MAC
            // check and stop image indicator
            // 查看和中止图画指示器
            if (finished) {
                [self sd_stopImageIndicator];
            }
#endif
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
            	//self是否被开释
                if (!self) { return; }
                if (!shouldNotSetImage) {
                    [self sd_setNeedsLayout];
                }
                //不要主动设置图片,调用Block传入UIImage目标
                if (completedBlock && shouldCallCompletedBlock) {
                    completedBlock(image, data, error, cacheType, finished, url);
                }
            };
            // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
            // 咱们得到了一个图画,但SDWebImageAvoidAutoSetImage标志被设置了
            // OR 或
            // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
            // 咱们没有图画,也没有设置SDWebImageDelayPlaceholder
            if (shouldNotSetImage) {
                dispatch_main_async_safe(callCompletedBlockClojure);
                return;
            }
            UIImage *targetImage = nil;
            NSData *targetData = nil;
            if (image) {
                // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
                // 咱们得到了一个图画,但SDWebImageAvoidAutoSetImage没有设置
                targetImage = image;
                targetData = data;
            } else if (options & SDWebImageDelayPlaceholder) {
                // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                // 咱们没有图画,而且设置了SDWebImageDelayPlaceholder标志
                targetImage = placeholder;
                targetData = nil;
            }
#if SD_UIKIT || SD_MAC
            // check whether we should use the image transition
            // 查看咱们是否应该运用图画过渡
            SDWebImageTransition *transition = nil;
            BOOL shouldUseTransition = NO;
            if (options & SDWebImageForceTransition) {
                // Always
                shouldUseTransition = YES;
            } else if (cacheType == SDImageCacheTypeNone) {
                // From network
                shouldUseTransition = YES;
            } else {
                // From disk (and, user don't use sync query)
                if (cacheType == SDImageCacheTypeMemory) {
                    shouldUseTransition = NO;
                } else if (cacheType == SDImageCacheTypeDisk) {
                	// SDWebImageQueryMemoryDataSync:
                    // 默许状况下,当您仅指定“SDWebImageQueryMemoryData”时,咱们会异步查询内存映像数据。
                    // 将此掩码也组合在一同,以同步查询内存图画数据
                    // 不主张同步查询数据,除非您要保证在同一 runloop 中加载映像以避免在单元重用期间闪耀。
                    // SDWebImageQueryDiskDataSync:
                    // 默许状况下,当内存缓存未射中时,咱们会异步查询磁盘缓存。此掩码能够强制同步查询磁盘缓存(当内存缓存未射中时)。
                    // 这两个选项打开则NO。
                    if (options & SDWebImageQueryMemoryDataSync || options & SDWebImageQueryDiskDataSync) {
                        shouldUseTransition = NO;
                    } else {
                        shouldUseTransition = YES;
                    }
                } else {
                    // Not valid cache type, fallback
                    shouldUseTransition = NO;
                }
            }
            if (finished && shouldUseTransition) {
                transition = self.sd_imageTransition;
            }
#endif
            dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
                [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
                [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif
                callCompletedBlockClojure();
            });
        }];
        // 将生成的加载操作赋给UIView的自界说特点,并赋值
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else {
#if SD_UIKIT || SD_MAC
        [self sd_stopImageIndicator];
#endif
        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);
            }
        });
    }
}

参数列表中,有一项SDWebImageOptions,在SDWebImageDefine.h里边,界说了SDWebImageOptions,其是一种暴露在外的可供运用者运用的挑选办法。

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    // 默许状况下,当URL下载失利时,URL会被列入黑名单,导致库不会再去重试,该符号用于禁用黑名单
    SDWebImageRetryFailed = 1 << 0,
    // 默许状况下,图片下载开端于UI交互,该符号禁用这一特性,这样下载推迟到UIScrollView减速时
    SDWebImageLowPriority = 1 << 1,
    // 该符号启用渐进式下载,图片在下载进程中是逐渐显现的,好像浏览器一下。
    // 默许状况下,图画在下载完结后一次性显现
    SDWebImageProgressiveLoad = 1 << 2,
    // 即使缓存了映像,也要遵从 HTTP 呼应缓存控件,并在需求时从长途位置改写映像。
    // 磁盘缓存将由 NSURLCache 而不是 SDWebImage 处理,然后导致功能略有下降。
    // 此选项有助于处理同一恳求URL后边的图画改变,例如Facebook图形API个人资料图片。
    // 假如改写缓存的图画,则对缓存的图画调用一次完结块,然后对终究图画调用一次完结块。
    // 仅当无法运用嵌入式缓存破坏参数使 URL 坚持静态时,才运用此标志。
    SDWebImageRefreshCached = 1 << 3,
    // 在iOS 4+体系中,当程序进入后台后持续下载图片。这将要求体系给予额外的时刻让恳求完结
    // 假如后台使命超时,则操作被撤销
    SDWebImageContinueInBackground = 1 << 4,
    // 经过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES;来处理存储在NSHTTPCookieStore中的cookie
    SDWebImageHandleCookies = 1 << 5,
    // 答应不受信赖的SSL认证
    // 用于测试意图。在生产中谨慎运用。
    SDWebImageAllowInvalidSSLCertificates = 1 << 6,
    // 默许状况下,图片下载按入队的顺序来履行。该符号将其移到行列的前面,
    // 以便图片能立即下载而不是比及当时行列被加载
    SDWebImageHighPriority = 1 << 7,
    // 默许状况下,占位图片在加载图片的一同被加载。该符号推迟占位图片的加载直到图片已以被加载完结
    SDWebImageDelayPlaceholder = 1 << 8,
    // 通常咱们不调用动画图片的transformDownloadedImage署理办法,由于大多数转化代码能够办理它。
    // 运用这个票房则不任何状况下都进行转化。
    SDWebImageTransformAnimatedImage = 1 << 9,
    // 默许状况下,图画在下载后增加到图画视图中。
    // 但在某些状况下,咱们期望在设置图画之前做一些事(have the hand)(例如,运用滤镜或增加穿插淡入淡出动画)
    // 假如要在成功时手动设置图画完结,请运用此标志
    SDWebImageAvoidAutoSetImage = 1 << 10,
	// 默许状况下,将依照其原始巨细对图画进行解码。
	// 此标志会将图画缩小到与设备受限内存兼容的巨细。
    // 要操控限制内存字节数,请选中“SDImageCoderHelper.defaultScaleDownLimitBytes”(在iOS上默许为60MB)
    // 这实践大将转化为运用v5.5.0中的上下文选项“.imageThumbnailPixelSize”(在iOS上默许为(3966,3966)。曾经没有。
    // 此标志也会影响 v5.5.0 中的逐行图画和动画图画。曾经没有。
    //@note 假如您需求细节控件,最好改用上下文选项“imageThumbnailPixelSize”和“imagePreserveAspectRatio”。
    SDWebImageScaleDownLargeImages = 1 << 11,
    // 默许状况下,当图画已缓存在内存中时,咱们不会查询图画数据。此掩码能够强制一同查询图画数据。可是,此查询是异步的,除非您指定“SDWebImageQueryMemoryDataSync”
    SDWebImageQueryMemoryData = 1 << 12,
    // 默许状况下,当您仅指定“SDWebImageQueryMemoryData”时,咱们会异步查询内存映像数据。将此掩码也组合在一同,以同步查询内存图画数据。
    //@note 不主张同步查询数据,除非您要保证在同一 runloop 中加载映像以避免在单元重用期间闪耀。
    SDWebImageQueryMemoryDataSync = 1 << 13,
    // 默许状况下,当内存缓存未射中时,咱们会异步查询磁盘缓存。此掩码能够强制同步查询磁盘缓存(当内存缓存未射中时)。
    //@note 这 3 个查询选项能够组合在一同。有关这些掩码组合的完整列表,请参阅 wiki 页面。
    //@note 不主张同步查询数据,除非您期望保证在同一 runloop 中加载映像以避免在单元重用期间闪耀。
    SDWebImageQueryDiskDataSync = 1 << 14,
    // 默许状况下,当缓存丢掉时,将从加载程序加载映像。此标志能够避免仅从缓存加载。
    SDWebImageFromCacheOnly = 1 << 15,
    // 默许状况下,咱们在从加载程序加载图画之前查询缓存。此标志能够避免仅从加载程序加载。
    SDWebImageFromLoaderOnly = 1 << 16,
    // 默许状况下,当您运用“SDWebImageTransition”在映像加载完结后履行某些视图转化时,仅当办理器的回调是异步的(来自网络或磁盘缓存查询)时,此转化才运用于映像。
    // 此掩码能够强制对任何状况(如内存缓存查询或同步磁盘缓存查询)运用视图转化。
    SDWebImageForceTransition = 1 << 17,
    // 默许状况下,咱们将在缓存查询期间解码后台的图画,并从网络下载。这有助于提高功能,由于在屏幕上呈现图画时,需求首要对其进行解码。但这发生在Core Animation的主行列上。
    // 可是,此进程也或许增加内存运用量。假如由于内存消耗过多而遇到问题,此标志或许会阻挠解码图画。
    SDWebImageAvoidDecodeImage = 1 << 18,
    // 默许状况下,咱们对动画图画进行解码。此标志能够仅强制解码第一帧并生成静态图画。
    SDWebImageDecodeFirstFrameOnly = 1 << 19,
    // 默许状况下,关于“SDAnimatedImage”,咱们在烘托期间解码动画图画帧以减少内存运用量。可是,您能够指定将一切帧预加载到内存中,以便在动画图画由很多 imageView 共享时下降 CPU 运用率。
    // 这实践上会在后台行列中触发“preloadAllAnimatedImageFrames”(仅限磁盘缓存和下载)。
    SDWebImagePreloadAllFrames = 1 << 20,
    // 默许状况下,当您运用“SDWebImageContextAnimatedImageClass”上下文选项时(例如运用旨在运用“SDAnimatedImage”的“SDAnimatedImageView”),当内存缓存射中时,咱们或许仍运用“UIImage”,或许图画解码器无法生成与您的自界说类彻底匹配的解码器作为回退解决方案
    // 运用此选项,能够保证咱们一直回调映像与您供给的类。假如无法生成一个,则将运用代码为“SDWebImageErrorBadImageData”的过错。
    // 请留意,此选项与“SDWebImageDecodeFirstFrameOnly”不兼容,后者一直生成 UIImage/NSImage。
    SDWebImageMatchAnimatedImageClass = 1 << 21,
    // 默许状况下,当咱们从网络加载图画时,图画将被写入缓存(内存和磁盘,由您的“storeCacheType”上下文选项操控)
    // 这或许是一个异步操作,终究的“SDInternalCompletionBlock”回调并不能保证写入的磁盘缓存已完结,并或许导致逻辑过错。(例如,您刚刚在完结块中修正磁盘数据,可是,磁盘缓存尚未准备就绪)
     // 假如需求运用完结块中的磁盘缓存进行处理,则应运用此选项来保证在回调时已写入磁盘缓存。
     // 请留意,假如在运用自界说缓存序列化程序或运用转化器时运用它,咱们也会比及写入的输出图画数据完结。
    SDWebImageWaitStoreCache = 1 << 22,
    // 咱们通常不会对矢量图画运用变换,由于矢量图画支撑动态改变到任何巨细,光栅化到固定巨细会丢掉细节。要修正矢量图画,能够在运行时处理矢量数据(例如修正PDF标签/ SVG元素)。
    // 无论怎么,运用此标志来转化它们。
    SDWebImageTransformVectorImage = 1 << 23
};

慢慢看这个函数的完结,看到:

NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
//从context获取对应的validOperationKey,然后依据参数validOperationKey进行后边的操作,
//假如operationKey为nil key取NSStringFromClass([self class])
if (!validOperationKey) {
	// pass through the operation key to downstream, which can used for tracing operation or image view class
	validOperationKey = NSStringFromClass([self class]);
	SDWebImageMutableContext *mutableContext = [context mutableCopy];
	mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
	context = [mutableContext copy];
}
//设置sd_latestOperationKey,最新的operationKey
self.sd_latestOperationKey = validOperationKey;

看最终一行:self.sd_latestOperationKey = validOperationKey;,能够看到,点语法实践在调用settergetter办法,运用objc_getAssociatedObjectobjc_setAssociatedObject函数。

- (nullable NSString *)sd_latestOperationKey {
    return objc_getAssociatedObject(self, @selector(sd_latestOperationKey));
}
- (void)setSd_latestOperationKey:(NSString * _Nullable)sd_latestOperationKey {
    objc_setAssociatedObject(self, @selector(sd_latestOperationKey), sd_latestOperationKey, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

有一个函数叫:sd_cancelCurrentImageLoad

- (void)sd_cancelCurrentImageLoad {
    [self sd_cancelImageLoadOperationWithKey:self.sd_latestOperationKey];
    self.sd_latestOperationKey = nil;
}

这儿边调用sd_cancelImageLoadOperationWithKey:办法时运用了sd_latestOperationKey。再接着前面的内容向下看,这儿直接调用了sd_cancelImageLoadOperationWithKey:办法:

//撤销从前的下载使命
[self sd_cancelImageLoadOperationWithKey:validOperationKey];

咱们从中不难发现,这句作用其实和在这儿直接运用[self sd_cancelCurrentImageLoad];作用相同。

这句话经过键值撤销当时图画的加载,键值是UIImageViewImageLoad,咱们持续点开下一层看到sd_cancelImageLoadOperationWithKey这个办法的完结

- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    if (key) {
        // Cancel in progress downloader from queue
        // 从行列中撤销正在进行的下载程序
        SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
        id<SDWebImageOperation> operation;
        @synchronized (self) {
            operation = [operationDictionary objectForKey:key];
        }
        if (operation) {
            if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
                [operation cancel];
            }
            @synchronized (self) {
                [operationDictionary removeObjectForKey:key];
            }
        }
    }
}
// 先获得办法一的回来字典数据,传入 key 在回来的字典中查找是否已经存在,假如存在则撤销一切操作
// conformsToProtocol 办法假如契合这个协议(协议中声明了撤销办法),调用协议中的撤销办法。

接下来了解一下其是怎样完结撤销行列中一切操作的下载。

NSMutableDictionary *operationDictionary = [self operationDictionary];

咱们点开 sd_operationDictionary的源码:

- (SDOperationsDictionary *)sd_operationDictionary {
    @synchronized(self) {
        SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
        if (operations) {
            return operations;
        }
        operations = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
        objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        return operations;
    }
}

这两个办法用的是 OC 中 runtime 办法,原理是两个文件相关办法,和上层的存储办法差不多,传入 valuekey 对应,取出也是依据 key 取出 valueobject 传入 self 即可。

// 取出办法
// 传入 object 和 key 回来 value
id objc_getAssociatedObject(id object, const void *key)
// 设置相关办法,为现有目标增加新特点,在本处是为self增加
// 传入 object、key、value,policy
// 该函数中第一位参数表明目标目标, 
// 第二个参数,key。由于一个目标能够相关多个新的对像,咱们需求一个标志来区别他们。
// 所以这个key就起这样的作用。这儿的需求的key的地址,不关心它指向谁。 
// 第三个参数表明要增加的特点, 
// 第四个参数设置objc_AssociationPolicy
// policy 即存储方式,和声明运用几种特点大致相同,有 copy,retain,copy,retain_nonatomic,assign 五种)
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

经过键值来得到绑定的的相关目标(分类,能够经过它来扩展办法;相关目标,能够经过它来扩展特点)的值赋值给可变字典类型的operations,假如operations不为空,那么回来该相关目标的值。暂时就这样了解。假如为空,就经过键值与相关战略(传递nil以铲除现有相关)为给定目标设定相关值。这便是operationDictionary的完结。 再说回来咱们来看一下撤销行列中下载的完结,其先为每个每一个UIView+WebCache 创立operationDictionary。假定咱们第一次进行这个操作,所以其不会进入第一个if中进行判别,由于一开端默许的loadOperationKey为静态变量,没有进行赋值操作。自然也没有key值,所以后边的if也不会走。 同样咱们也看一下假如前面已经有相同的key值其是怎样进行撤销与判别的,后边的代码。 假如有两个键值相同的内容进入行列,那么经过前面字典的创立得到的相关目标的值也是相同的,传入key在回来的字典中查找是否已经存在,假如存在则撤销一切操作,conformsToProtocol办法假如契合这个协议(协议中声明了撤销办法),也调用协议中的撤销办法。 实践上,一切的操作都是由一个实践上,一切的操作都是由一个operationDictionary字典保护的,履行新的操作之前,cancel一切的operation这样做好处多多:

  • 针对一个UIImageView,撤销前一个操作,省时、省流量
  • 也便是避免对一张图片进行重复下载。加载图片完结后, 回调时会先查看使命的Operation还在不在, 不在,则不回调显现, 反之回调显现并移除Operation
  • 当程序中止导致链接失效时,当时的下载还在操作行列中,可是按道理应该是失效状况,咱们能够经过先撤销当时正在进行的下载来保证操作行列中的链接不存在这种状况。

撤销操作后,直接是一句:self.sd_imageURL = url;。这句话运用点语法,暗藏玄机:

- (nullable NSURL *)sd_imageURL {
    return objc_getAssociatedObject(self, @selector(sd_imageURL));
}
- (void)setSd_imageURL:(NSURL * _Nullable)sd_imageURL {
    objc_setAssociatedObject(self, @selector(sd_imageURL), sd_imageURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

其实也调用了objc_setAssociatedObject函数。以每个需求下载的图画的url为加载的键值进行绑定。这样每个都有自己所对应的键值。 接下来向下看:

if (!(options & SDWebImageDelayPlaceholder)) {
	dispatch_main_async_safe(^{
		[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
	});
}

前面提到,SDWebImageDelayPlaceholder是一个option选项,其为枚举类型:

// 默许状况下,占位图片在加载图片的一同被加载。该符号推迟占位图片的加载直到图片已以被加载完结
SDWebImageDelayPlaceholder = 1 << 8,

dispatch_main_async_safe是个宏界说,保证在主线程安全履行。

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
    if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }
#endif

主线程调用了:

- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL;

查看代码:

- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL {
#if SD_UIKIT || SD_MAC
    [self sd_setImage:image imageData:imageData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:nil cacheType:cacheType imageURL:imageURL];
#else
    // watchOS does not support view transition. Simplify the logic
    if (setImageBlock) {
        setImageBlock(image, imageData, cacheType, imageURL);
    } else if ([self isKindOfClass:[UIImageView class]]) {
        UIImageView *imageView = (UIImageView *)self;
        [imageView setImage:image];
    }
#endif
}

办法先是进行开发环境查看,SDWebImage 支撑当时的 MAC / iOS / TV / WATCH 渠道,这种适配各个渠道的兼容,对结构开发意义严重。下面扼要介绍一下各个标签的含义:

#if !TARGET_OS_IPHONE && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_WATCH
    #define SD_MAC 1
#else
    #define SD_MAC 0
#endif
// 判别当时渠道是不是MAC。
// TARGET_OS_MAC 界说在一切的渠道中,比方MAC、iPhone、Watch、TV等,因而单纯的运用TARGET_OS_MAC 判别当时是不是MAC 渠道是不可行的。
// 但依照上面的判别方式,也存在一个缺点:当Apple呈现新的渠道时,判别条件要修正。
#if TARGET_OS_IOS || TARGET_OS_TV
    #define SD_UIKIT 1
#else
    #define SD_UIKIT 0
#endif
// iOS 和 tvOS 是非常相似的,UIKit在这两个渠道中都存在,可是watchOS在运用UIKit时,是受限的。
// 因而界说SD_UIKIT为真的条件是iOS 和 tvOS这两个渠道。
#if TARGET_OS_IOS
    #define SD_IOS 1
#else
    #define SD_IOS 0
#endif
#if TARGET_OS_TV
    #define SD_TV 1
#else
    #define SD_TV 0
#endif
#if TARGET_OS_WATCH
    #define SD_WATCH 1
#else
    #define SD_WATCH 0
#endif
// 这三个宏界说用于区别iOS、TV、WATCH三个渠道。

关于这些,咱们就知道是为了不同环境适配就好了。不是WATCH的状况下调用了:

- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock transition:(SDWebImageTransition *)transition cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL;

在这个办法里,对self类进行查看,

	...
} else if ([view isKindOfClass:[UIImageView class]]) {
	UIImageView *imageView = (UIImageView *)view;
	finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData, SDImageCacheType setCacheType, NSURL *setImageURL) {
	};
} ...

几个if状况看下来,咱们不难看出,这便是为不同的类的self设置图片。后边还有个if (transition)对是否需求过渡。分析比较冗长,到目前为止便是:动态绑定validOperationKey->先撤销之前的下载->动态绑定imageURL->判别并依据要求设置占位图片。 接下来,咱们看到if (url),检测传入的URL是否为空。接下来是重置进展数据。

NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
if (imageProgress) {
	imageProgress.totalUnitCount = 0;
	imageProgress.completedUnitCount = 0;
}

接下来是[self sd_startImageIndicator];,查看sd_startImageIndicator代码:

- (void)sd_startImageIndicator {
    id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
    if (!imageIndicator) {
        return;
    }
    dispatch_main_async_safe(^{
        [imageIndicator startAnimatingIndicator];
    });
}

再查看startAnimatingIndicator代码:

- (void)startAnimatingIndicator {
#if SD_UIKIT
    [self.indicatorView startAnimating];
#else
    [self.indicatorView startAnimation:nil];
#endif
    self.indicatorView.hidden = NO;
}

查看界说:

#if SD_UIKIT
@property (nonatomic, strong, readonly, nonnull) UIActivityIndicatorView *indicatorView;
#else
@property (nonatomic, strong, readonly, nonnull) NSProgressIndicator *indicatorView;
#endif

咱们知道了indicatorView是加载小菊花,这句代码的意思便是让它依据状况挑选加载小菊花。假如没见过小菊花,那应该是加载的太快了,导致小菊花还没有加载出来就完毕了,而且白底的确看不清。再向下看,先获取图画办理者目标,假如自界说了图画办理者目标就用自界说的,不然就用单例目标。接下来,==创立代码块用来处理进展数据==,然后回调block。 再接下来,==创立图画加载操作==,图画办理者目标manager调用

- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                          options:(SDWebImageOptions)options
                                          context:(nullable SDWebImageContext *)context
                                         progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                        completed:(nonnull SDInternalCompletionBlock)completedBlock;

里边拆分成下面6个办法:

  • callCacheProcessForOperation
  • callDownloadProcessForOperation
  • callStoreCacheProcessForOperation
  • callTransformProcessForOperation
  • callCompletionBlockForOperation
  • safelyRemoveOperationFromRunning

分别是:缓存查询、下载、存储、转化、履行回调、整理回调。 查看该办法代码:

- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                          options:(SDWebImageOptions)options
                                          context:(nullable SDWebImageContext *)context
                                         progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                        completed:(nonnull SDInternalCompletionBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    // 回调block是必传的
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
    // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
    // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
    // 尽管参数要求传NSURL类型目标可是假如传NSStriing类型目标并不会有警告,所以再做一下处理
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }
    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    // 避免参数url的类型过错导致崩溃,例如url的是NSNull类型的
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }
	// 生成一个图画加载操作的封装目标
    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    operation.manager = self;
	// 界说变量保存要加载的url是否失利过
    BOOL isFailedUrl = NO;
    if (url) {
    	// 加锁
        SD_LOCK(self.failedURLsLock);
        // 判别是否失利过
        isFailedUrl = [self.failedURLs containsObject:url];
        SD_UNLOCK(self.failedURLsLock);
    }
	// 假如链接地址不正确,
    // 或许之前加载失利过而且没有设置失利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;
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] url:url];
        return operation;
    }
	// 加锁
    SD_LOCK(self.runningOperationsLock);
    // 保存当时操作封装目标到特点中
    [self.runningOperations addObject:operation];
    SD_UNLOCK(self.runningOperationsLock);
    // Preprocess the options and context arg to decide the final the result for manager
    // 预处理选项和上下文参数,以确定办理器的终究成果
    SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
    // Start the entry to load image from cache
    // 发动条目以从缓存加载图画
    [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
    return operation;
}

这儿,咱们看到了manage的第一层的调用,这层调用能够直观的看出,回来了一个SDWebImageOperation图画加载操作,假如出错了(即假如链接地址不正确,或许之前加载失利过而且没有设置失利url重试选项)就直接回调过错并回来。这一层下来,咱们仍然没有看到它从内存获取或许从网路下载的实践进程,接下来,咱们看它里边的第二层调用:

// 发动条目以从缓存加载图画
[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];

调用了

- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                 url:(nonnull NSURL *)url
                             options:(SDWebImageOptions)options
                             context:(nullable SDWebImageContext *)context
                            progress:(nullable SDImageLoaderProgressBlock)progressBlock
                           completed:(nullable SDInternalCompletionBlock)completedBlock;

咱们看它的完结:

// Query normal cache process
// 查询正常缓存进程
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                 url:(nonnull NSURL *)url
                             options:(SDWebImageOptions)options
                             context:(nullable SDWebImageContext *)context
                            progress:(nullable SDImageLoaderProgressBlock)progressBlock
                           completed:(nullable SDInternalCompletionBlock)completedBlock {
    // Grab the image cache to use
    // 获取要运用的图画缓存
    id<SDImageCache> imageCache;
    if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
        imageCache = context[SDWebImageContextImageCache];
    } else {
        imageCache = self.imageCache;
    }
    // Get the query cache type
    // 获取查询缓存类型
    SDImageCacheType queryCacheType = SDImageCacheTypeAll;
    if (context[SDWebImageContextQueryCacheType]) {
        queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue];
    }
    // Check whether we should query cache
    // 查看是否应该查询缓存
    BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
    if (shouldQueryCache) {// 查询缓存
        // 获取url对应的key
        NSString *key = [self cacheKeyForURL:url context:context];
        // 经过缓存目标查询url对应的缓存
        @weakify(operation);
        operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
            @strongify(operation);
            // 假如图画加载操作封装目标不存在,
            // 或许图画加载操作封装目标被撤销,
            // 就从图画加载操作封装目标调集特点中移除并回来
            if (!operation || operation.isCancelled) {
                // Image combined operation cancelled by user
                // 用户撤销的图画组合操作
                [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url];
                [self safelyRemoveOperationFromRunning:operation];
                return;
            } else if (context[SDWebImageContextImageTransformer] && !cachedImage) {
                // Have a chance to query original cache instead of downloading
                // 有时机查询原始缓存而不是下载
                // 在该办法内部仍有时机触发下载办法
                [self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
                return;
            }
            // Continue download process
            // 持续下载进程
            [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
        }];
    } else { // 不查询缓存
        // Continue download process
        // 持续下载进程
        [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
    }
}

咱们能够看到,这一办法内部进行了更细的区分,将逻辑打散,一个办法接口,终究意图是回调一个完结块(completedBlock)。先分成两个首要逻辑部分:
第一个是应该查询缓存: 调用了

- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock {
    if (!key) {
        return nil;
    }
    NSArray<id<SDImageCache>> *caches = self.caches;
    NSUInteger count = caches.count;
    if (count == 0) {
        return nil;
    } else if (count == 1) {
        return [caches.firstObject queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
    }
    switch (self.queryOperationPolicy) {
        case SDImageCachesManagerOperationPolicyHighestOnly: {
            id<SDImageCache> cache = caches.lastObject;
            return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
        }
            break;
        case SDImageCachesManagerOperationPolicyLowestOnly: {
            id<SDImageCache> cache = caches.firstObject;
            return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
        }
            break;
        case SDImageCachesManagerOperationPolicyConcurrent: {
            SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
            [operation beginWithTotalCount:caches.count];
            [self concurrentQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
            return operation;
        }
            break;
        case SDImageCachesManagerOperationPolicySerial: {
            SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
            [operation beginWithTotalCount:caches.count];
            [self serialQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
            return operation;
        }
            break;
        default:
            return nil;
            break;
    }
}

经过一系列逻辑判别与调用,对图画数据进行了查询,最终一层的查询完毕后,完毕块回来了三个参数:UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType。经过判别,假如能够从缓存获取图片,则回来,如无法从缓存获取图片,则进行下载。
第二个是不应该查询缓存: 不查询缓存则直接调用下载办法。 下面看看下载的流程:

// SDWebImageManager.h
// 下载流程
- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                    url:(nonnull NSURL *)url
                                options:(SDWebImageOptions)options
                                context:(SDWebImageContext *)context
                            cachedImage:(nullable UIImage *)cachedImage
                             cachedData:(nullable NSData *)cachedData
                              cacheType:(SDImageCacheType)cacheType
                               progress:(nullable SDImageLoaderProgressBlock)progressBlock
                              completed:(nullable SDInternalCompletionBlock)completedBlock {
    // Grab the image loader to use
    // 抓取要运用的图画加载器
    id<SDImageLoader> imageLoader;
    if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
        imageLoader = context[SDWebImageContextImageLoader];
    } else {
        imageLoader = self.imageLoader;
    }
    // Check whether we should download image from network
    // 查看咱们是否应该从网络下载图画
    BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
    // SDWebImageFromCacheOnly:默许状况下,当缓存丢掉时,将从加载程序加载映像。此标志能够避免仅从缓存加载。
    shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
    shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
    shouldDownload &= [imageLoader canRequestImageForURL:url];
    if (shouldDownload) {
        if (cachedImage && options & SDWebImageRefreshCached) {
            // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
            // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
            // 假如在缓存中找到图画,但供给了SDWebImageRefreshCached,请告诉缓存的图画并测验从头下载它,
            // 以便有时机经过NSURLCache从服务器改写它。
            [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            // Pass the cached image to the image loader. The image loader should check whether the remote image is equal to the cached image.
            // 将缓存的映像传递到映像加载程序。映像加载程序应查看长途映像是否等于缓存的映像。
            SDWebImageMutableContext *mutableContext;
            if (context) {
                mutableContext = [context mutableCopy];
            } else {
                mutableContext = [NSMutableDictionary dictionary];
            }
            mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
            context = [mutableContext copy];
        }
        // 敞开下载使命
        @weakify(operation);
        operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
            @strongify(operation);
            if (!operation || operation.isCancelled) {
                // Image combined operation cancelled by user
                // 用户撤销的图画组合操作
                [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] url:url];
            } else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
                // SDWebImageErrorCacheNotModified : The remote location specify that the cached image is not modified, such as the HTTP response 304 code. It's useful for `SDWebImageRefreshCached`
                // 长途位置指定不修正缓存的图画,如 HTTP 呼应 304 代码。它关于“SDWebImageRefreshCached”很有用
                // Image refresh hit the NSURLCache cache, do not call the completion block
                // 图画改写射中 NSURL缓存,不调用完结块
            } else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) {
                // Download operation cancelled by user before sending the request, don't block failed URL
                // 用户在发送恳求前撤销下载操作,请勿阻挠失利的URL
                [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
            } else if (error) {
                [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
                BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context];
                if (shouldBlockFailedURL) {
                    SD_LOCK(self.failedURLsLock);
                    [self.failedURLs addObject:url];
                    SD_UNLOCK(self.failedURLsLock);
                }
            } else {
                if ((options & SDWebImageRetryFailed)) {
                    SD_LOCK(self.failedURLsLock);
                    [self.failedURLs removeObject:url];
                    SD_UNLOCK(self.failedURLsLock);
                }
                // Continue store cache process
                // 持续存储缓存进程
                [self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
            }
            if (finished) {
                [self safelyRemoveOperationFromRunning:operation];
            }
        }];
    } else if (cachedImage) {
        [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
        [self safelyRemoveOperationFromRunning:operation];
    } else {
        // Image not in cache and download disallowed by delegate
        // 图画不在缓存中,而且署理不答应下载
        [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
        [self safelyRemoveOperationFromRunning:operation];
    }
}

咱们要点重视其间敞开下载使命的部分,调用了

// SDWebImageLoadersManager.m
- (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDImageLoaderCompletedBlock)completedBlock {
	// url不存在
    if (!url) {
        return nil;
    }
    NSArray<id<SDImageLoader>> *loaders = self.loaders;
    for (id<SDImageLoader> loader in loaders.reverseObjectEnumerator) {
        if ([loader canRequestImageForURL:url]) {
            return [loader requestImageWithURL:url options:options context:context progress:progressBlock completed:completedBlock];
        }
    }
    return nil;
}

假如url正常,则持续调用

// SDWebImageDownloader.m
- (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDImageLoaderCompletedBlock)completedBlock {
    UIImage *cachedImage = context[SDWebImageContextLoaderCachedImage];
    // 创立变量保存图画下载的选项
    SDWebImageDownloaderOptions downloaderOptions = 0;
    // 依据设置的选项设置图画下载的选项
    if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
    if (options & SDWebImageProgressiveLoad) downloaderOptions |= SDWebImageDownloaderProgressiveLoad;
    if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
    if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
    if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
    if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
    if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
    if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
    if (options & SDWebImageAvoidDecodeImage) downloaderOptions |= SDWebImageDownloaderAvoidDecodeImage;
    if (options & SDWebImageDecodeFirstFrameOnly) downloaderOptions |= SDWebImageDownloaderDecodeFirstFrameOnly;
    if (options & SDWebImagePreloadAllFrames) downloaderOptions |= SDWebImageDownloaderPreloadAllFrames;
    if (options & SDWebImageMatchAnimatedImageClass) downloaderOptions |= SDWebImageDownloaderMatchAnimatedImageClass;
    // 假如有缓存图画,而且挑选了改写缓存的选项
    if (cachedImage && options & SDWebImageRefreshCached) {
        // force progressive off if image already cached but forced refreshing
        // 撤销渐进下载
        downloaderOptions &= ~SDWebImageDownloaderProgressiveLoad;
        // ignore image read from NSURLCache if image if cached but force refreshing
        // 忽视从NSURLCache中获取缓存
        downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
    }
    // 敞开下载使命并获取下载token
    return [self downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:completedBlock];
}

接下来查看最终调用的办法:

// SDWebImageDownloader.m
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
    // URL 将用作回调字典的键,因而它不能为 nil。假如为 nil,请立即调用没有图画或数据的已完结块。
    if (url == nil) {
        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;
    // 从缓存中获取url对应的操作目标
    // 测验复用之前生成的 operation
    NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
    // There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`.
    // 有一种状况是,该操作或许被符号为已完结或已撤销,但未从“self”中删去。URLOperations'.
    if (!operation || operation.isFinished || operation.isCancelled) {
    	// 创立出新的 operation 并存储在 URLOperations 中 。
    	// 一同会装备 completionBlock,使得使命完结后能够及时整理 URLOperations。
    	// 保存 progressBlock 和 completedBlock;
    	// 提交 operation 到 downloadQueue。
        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);
        operation.completionBlock = ^{
            // 操作完结时,从缓存中移除该url对应的操作目标
            @strongify(self);
            if (!self) {
                return;
            }
            SD_LOCK(self.operationsLock);
            [self.URLOperations removeObjectForKey:url];
            SD_UNLOCK(self.operationsLock);
        };
        // 缓存url和其对应的操作目标
        self.URLOperations[url] = operation;
        // Add the handlers before submitting to operation queue, avoid the race condition that operation finished before setting handlers.
        // 提交到操作行列前增加处理程序,避免设置处理程序前操作完结的争用条件。
        downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
        // Add operation to operation queue only after all configuration done according to Apple's doc.
        // 只要在依据Apple的文档完结一切装备后,才能将操作增加到操作行列中。
        // `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
        // “addOperation:”不会同步履行 “operation.completeBlock”,因而这不会导致死锁。
        // 将操作目标增加到操作行列中
        [self.downloadQueue addOperation:operation];
    } else {
        // When we reuse the download operation to attach more callbacks, there may be thread safe issue because the getter of callbacks may in another queue (decoding queue or delegate queue)
        // 当咱们重用下载操作来附加更多回调时,或许存在线程安全问题,由于回调的获取者或许在另一个行列(解码行列或委托行列)中。
        // So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.
        // 因而,咱们在这儿确定操作,在“SDWebImageDownloaderOperation”中,咱们运用“@synchonzied(self)”来保证这两个类之间的线程安全。
        @synchronized (operation) {
            downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
            // 这儿 addHandlersForProgress: 会将 progressBlock 与 completedBlock 一同存入 
            // NSMutableDictionary<NSString *, id> SDCallbacksDictionary 然后回来保存在 
            // downloadOperationCancelToken 中。
            // 别的,Operation 在 addHandlersForProgress: 
            // 时并不会铲除之前存储的 callbacks 是增量保存的,
            // 也便是说屡次调用的 callBack 在完结后都会被顺次履行。
        }
        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
    SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
    token.url = url;
    token.request = operation.request;
    token.downloadOperationCancelToken = downloadOperationCancelToken;
    // 终究 operation、url、request、downloadOperationCancelToken 
    // 一同被打包进 SDWebImageDownloadToken, 下载办法完毕。
    // 回来token
    return token;
}

看一眼下面else里一大堆注释那的- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock办法的完结:

// SDWebImageDownloaderOperation.m
static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed";
typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;
// 下载进展 完结 callback 存储到 数组中(callbackBlocks)或许会空
// 完结高吞吐量的数据读写保证线程安全
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                            completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
    if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
    if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
    @synchronized (self) {
        [self.callbackBlocks addObject:callbacks];
    }
    return callbacks;
}

看回去,之前的办法中调用的- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url options:(SDWebImageDownloaderOptions)options context:(nullable SDWebImageContext *)context办法:

// SDWebImageDownloader.m
- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url options:(SDWebImageDownloaderOptions)options context:(nullable SDWebImageContext *)context {
    // 获取下载恳求超时时刻,默许15秒
    NSTimeInterval timeoutInterval = self.config.downloadTimeout;
    if (timeoutInterval == 0.0) {
        timeoutInterval = 15.0;
    }
    // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
    // 为了避免潜在的重复缓存(NSURLCache + SDImageCache),假如还有说明,咱们将禁用图画恳求的缓存
    // 依据设置的选项不同,设置不同的恳求缓存战略
    NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
    // 创立恳求目标
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
    // Cookies处理方式
    mutableRequest.HTTPShouldHandleCookies = SD_OPTIONS_CONTAINS(options, SDWebImageDownloaderHandleCookies);
    // 默许运用管线化
    mutableRequest.HTTPShouldUsePipelining = YES;
    // 设置HTTP恳求头字段
    SD_LOCK(self.HTTPHeadersLock);
    mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
    SD_UNLOCK(self.HTTPHeadersLock);
    // Context Option
    // 上下文选项
    SDWebImageMutableContext *mutableContext;
    if (context) {
        mutableContext = [context mutableCopy];
    } else {
        mutableContext = [NSMutableDictionary dictionary];
    }
    // Request Modifier
    // 从 imageContext 中取出 id<SDWebImageDownloaderRequestModifier> requestModifier 对 request 进行改造。
    // 值得留意的是 requestModifier 的获取是有优先级的,
    // 经过 imageContext 得到的优先级高于 downloader 所拥有的。
    // 经过这种方既满足了接口调用方可控,又能支撑大局装备
    // 同理,id<SDWebImageDownloaderResponseModifier> responseModifier 、
    // 		id<SDWebImageDownloaderDecryptor> decryptor 也是如此。
    id<SDWebImageDownloaderRequestModifier> requestModifier;
    if ([context valueForKey:SDWebImageContextDownloadRequestModifier]) {
        requestModifier = [context valueForKey:SDWebImageContextDownloadRequestModifier];
    } else {
        requestModifier = self.requestModifier;
    }
    NSURLRequest *request;
    if (requestModifier) {
        NSURLRequest *modifiedRequest = [requestModifier modifiedRequestWithRequest:[mutableRequest copy]];
        // If modified request is nil, early return
        if (!modifiedRequest) {
            return nil;
        } else {
            request = [modifiedRequest copy];
        }
    } else {
        request = [mutableRequest copy];
    }
    // Response Modifier
    // 呼应修饰符
    id<SDWebImageDownloaderResponseModifier> responseModifier;
    if ([context valueForKey:SDWebImageContextDownloadResponseModifier]) {
        responseModifier = [context valueForKey:SDWebImageContextDownloadResponseModifier];
    } else {
        responseModifier = self.responseModifier;
    }
    if (responseModifier) {
        mutableContext[SDWebImageContextDownloadResponseModifier] = responseModifier;
    }
    // Decryptor
    // 解密器
    id<SDWebImageDownloaderDecryptor> decryptor;
    if ([context valueForKey:SDWebImageContextDownloadDecryptor]) {
        decryptor = [context valueForKey:SDWebImageContextDownloadDecryptor];
    } else {
        decryptor = self.decryptor;
    }
    if (decryptor) {
        mutableContext[SDWebImageContextDownloadDecryptor] = decryptor;
    }
    context = [mutableContext copy];
    // Operation Class
    // 最终,从 downloaderConfig 中取出 operationClass 创立 operation
    Class operationClass = self.config.operationClass;
    if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) {
        // Custom operation class
        // 自界说操作类
    } else {
        operationClass = [SDWebImageDownloaderOperation class];
    }
    NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];
    if ([operation respondsToSelector:@selector(setCredential:)]) {
        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];
        }
    }
    if ([operation respondsToSelector:@selector(setMinimumProgressInterval:)]) {
        operation.minimumProgressInterval = MIN(MAX(self.config.minimumProgressInterval, 0), 1);
    }
    if (options & SDWebImageDownloaderHighPriority) {
        operation.queuePriority = NSOperationQueuePriorityHigh;
    } else if (options & SDWebImageDownloaderLowPriority) {
        operation.queuePriority = NSOperationQueuePriorityLow;
    }
    if (self.config.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
        // Emulate LIFO execution order by systematically, each previous adding operation can dependency the new operation
        // 经过体系地模拟后进先出的履行顺序,每个从前增加的操作都能够依靠于新操作
        // This can gurantee the new operation to be execulated firstly, even if when some operations finished, meanwhile you appending new operations
        // 这能够保证首要履行新操作,即使某些操作完结,一同附加新操作
        // Just make last added operation dependents new operation can not solve this problem. See test case #test15DownloaderLIFOExecutionOrder
        // 仅仅让最终增加的操作依靠新操作不能解决这个问题。查看测试用例#test15DownloaderLIFOExecutionOrder
        for (NSOperation *pendingOperation in self.downloadQueue.operations) {
            [pendingOperation addDependency:operation];
        }
        //默许状况下,每个使命是依照 FIFO 顺序增加到 downloadQueue 中,
        //假如用户设置的是 LIFO 时,增加进行列前会修正行列中现有使命的优先级来到达作用
        //经过遍历行列,将新使命修正为当时行列中一切使命的依靠以反转优先级
    }
    return operation;
}

总体看下来,SDWebImageDownloader这个类从字面上了解是一个下载器类,实践看代码,发现这个类并没有做实践的下载,而是保护着一个NSURLSession目标和一个NSOperationQueue目标,办理着一些SDWebImageDownloaderOperation目标。实践的下载应该是在SDWebImageDownloaderOperation类里完结的。这个是后话了。 SDWebImageDownloader经过传入的参数生成一个SDWebImageDownloaderOperation目标,并增加到操作行列中。详细的网络下载操作则由SDWebImageDownloaderOperation目标自己处理。 接下来回到前面的

- (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;

办法里边。在loadImageWithURL:options:context:progress:completed:的回调block中,接纳到了如下参数:UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL。如完结,则中止小菊花。 接下来进行判别:

// 假如完结,或许没完结可是设置了手动设置图画的选项,就需求调用完结block
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
// 假如有图而且设置了手动设置图画的选项,
// 或许没有图而且没设置推迟加载占位图的选项,
// 就不需求设置图画
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));

接下来判别是否需求图画以及需求的图画是什么:

// 假如不需求设置图画就直接回调完结状况
if (shouldNotSetImage) {
	dispatch_main_async_safe(callCompletedBlockClojure);
	return;
}
// 创立变量保存图画目标和图画数据
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
	// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
	// 假如有图就直接保存
    targetImage = image;
    targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
	// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
	// 假如没图可是设置了推迟加载占位图的选项,就职保存占位图
	targetImage = placeholder;
	targetData = nil;
}

再对加载动画进行操作,最终在主线程改写:

dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
	// 正常状况下就直接主线程异步调用设置图画并回调完结状况
	[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
	[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif
	callCompletedBlockClojure();
});

以上便是完结的首要流程。 在 SDWebImageManager核心办法中触及到两个重要的类SDWebImageDownloaderSDImageCache,下面将进行解说:

SDWebImageDownloader

这个类的办法咱们前面已经看过一些了,也说了详细的网络下载操作是由SDWebImageDownloaderOperation目标处理。图片的下载是放在一个NSOperationQueue操作行列中来完结的,其声明如下:

@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;

默许状况下,行列最大并发数是6。假如需求的话,能够经过SDWebImageDownloader类的maxConcurrentDownloads特点来修正。图片的下载回调经过block完结。

// SDImageDownLoader.h
typedef void(^SDImageLoaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL);
typedef void(^SDImageLoaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished);
// SDWebImageDownloader.h
typedef SDImageLoaderProgressBlock SDWebImageDownloaderProgressBlock;
typedef SDImageLoaderCompletedBlock SDWebImageDownloaderCompletedBlock;

咱们看一下- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url options:(SDWebImageDownloaderOptions)options context:(nullable SDWebImageContext *)context办法的代码,里边有这么一行:

// 创立下载操作目标
NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];
// 初始化没有什么特别的,需求留意的是这儿传入的 nullable session 是以 unownedSessin 保存,
// 区别于内部默许生成的 ownedSession。假如初始化时 session 为空,会在 start 时创立 ownedSession。

这个operation其实便是咱们下载操作的目标。这儿运用了NSOperation目标来进行多线程工作。这儿的operation目标是归于NSOperation的子类SDWebImageDownloaderOperation。咱们简略回忆一下NSOperation的一些要点:

  • NSOperation有两个办法:main()start()。假如想运用同步,那么最简略办法的便是把逻辑写在main()中,运用异步,需求把逻辑写到start()中,然后加入到行列之中。
  • NSOperation什么时分履行呢?依照正常主意,是手动调用main()start(),当然这样也能够。当调用start()的时分,默许的是在当时线程履行同步操作,假如是在主线程调用了,那么必然会导致程序死锁。别的一种办法便是加入到operationQueue中,operationQueue会尽快履行NSOperation,假如operationQueue是同步的,那么它会比及NSOperationisFinished等于YES后,再履行下一个使命,假如是异步的,经过设置maxConcurrentOperationCount来操控一同履行的最大操作,某个操作完结后,持续其他的操作。当然假如NSOperationQueuemaxConcurrentOperationCount假如设置为1的话,进相当于FIFO了。
  • 行列是怎样调用咱们的履行的操作的呢?假如你仅仅想弄一个同步的办法,那很简略,你只要重写main()这个函数,在里边增加你要的操作。假如想界说异步的办法的话就重写start()办法。在你增加进operationQueue中的时分体系将主动调用你这个start()办法,这时将不再调用main里边的办法。
  • 并不是调用了cancel就一定撤销了,假如NSOperation没有履行,那么就会撤销,假如履行了,只会将isCancelled设置为YES。所以,在咱们的操作中,咱们应该在每个操作开端前,或许在每个有意义的实践操作完结后,先查看下这个特点是不是已经设置为YES。假如是YES,则后边操作都能够不用再履行了。

经过观察- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url options:(SDWebImageDownloaderOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock的代码,咱们能够看出来,SDWebImageDownloaderoperation加到行列中履行:

// 将操作目标增加到操作行列中
[self.downloadQueue addOperation:operation];

上面两行代码便是SDWebImageDownloader敞开下载图片使命的==关键==:

  1. 生成下载使命的Operation操作;
  2. 将Operation操作加入到行列中,完结异步使命下载;

SDWebImageDownloaderOperation

咱们接下来看一看SDWebImageDownloaderOperation重写的start()办法:

// 当SDWebImageDownloaderOperation增加到并发行列,就会调用start办法。
- (void)start {
    @synchronized (self) {
        // 确认当时下载状况
        if (self.isCancelled) {
            self.finished = YES;
            // 假如当时下载状况是完结 则设置下载完结状况为YES 而且完毕下载使命
            // Operation cancelled by user before sending the request
            [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user before sending the request"}]];
            [self reset];
            return;
        }
#if SD_UIKIT
        // 注册后台使命
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak typeof(self) wself = self;
            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                [wself cancel];
            }];
        }
#endif
        NSURLSession *session = self.unownedSession;
        if (!session) {
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            /**
             *  Create the session for this task
             *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
             *  method calls and completion handler calls.
             */
            session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                    delegate:self
                                               delegateQueue:nil];
            self.ownedSession = session;
        }
        if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
            // Grab the cached data for later check
            NSURLCache *URLCache = session.configuration.URLCache;
            if (!URLCache) {
                URLCache = [NSURLCache sharedURLCache];
            }
            NSCachedURLResponse *cachedResponse;
            // NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483
            @synchronized (URLCache) {
                cachedResponse = [URLCache cachedResponseForRequest:self.request];
            }
            if (cachedResponse) {
                self.cachedData = cachedResponse.data;
            }
        }
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES;
    }
    if (self.dataTask) {
        if (self.options & SDWebImageDownloaderHighPriority) {
            self.dataTask.priority = NSURLSessionTaskPriorityHigh;
            self.coderQueue.qualityOfService = NSQualityOfServiceUserInteractive;
        } else if (self.options & SDWebImageDownloaderLowPriority) {
            self.dataTask.priority = NSURLSessionTaskPriorityLow;
            self.coderQueue.qualityOfService = NSQualityOfServiceBackground;
        } else {
            self.dataTask.priority = NSURLSessionTaskPriorityDefault;
            self.coderQueue.qualityOfService = NSQualityOfServiceDefault;
        }
        // 开端下载使命
        [self.dataTask resume];
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
        }
        __block typeof(self) strongSelf = self;
        // 下载开端告诉
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:strongSelf];
        });
    } else {
        // 假如下载使命实例化失利,则以过错的状况调用completedBlock回调
        [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
        [self done];
    }
}

本类遵守了NSURLSessionTaskDelegateNSURLSessionDataDelegate两个协议,并完结了署理办法。 NSURLSessionDataDelegate是 task 等级的协议,首要用来处理 data 和 upload,如接纳到呼应,接纳到数据,是否缓存数据。

// 每次回来新的数据时会调用该办法,开发者需求在该办法中合理地处理从前的数据,
// 不然会被新数据覆盖。假如没有供给该办法的完结,那么session将会持续使命,也便是说会覆盖之前的数据。
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;

didReceiveResponse 时,会保存 response.expectedContentLength 作为 expectedSize。然后调用 modifiedResponseWithResponse: 保存修改后的 reponse

// 客户端已收到服务器回来的部分数据
// data 自上次调用以来收到的数据
// 该办法或许被屡次调用,而且每次调用只供给自上次调用以来收到的数据;
// 因而 NSData 通常是由许多不同的data拼凑在一同的,所以尽量运用 [NSData enumerateByteRangesUsingBlock:] 办法迭代数据,
// 而非 [NSData getBytes]
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;

每次 didReceiveData 会将 data 追加到 imageData[self.imageData appendData:data] ,更新 receivedSizeself.receivedSize = self.imageData.length 。终究,当 receivedSize > expectedSize 断定下载完结,履行后续处理。假如你支撑了 SDWebImageDownloaderProgressiveLoad,每逢收到数据时,将会进入 coderQueue 进行边下载边解码:

NSData *imageData = [self.imageData copy];
// keep maximum one progressive decode process during download
if (self.coderQueue.operationCount == 0) {
	// NSOperation have autoreleasepool, don't need to create extra one
	[self.coderQueue addOperationWithBlock:^{
		UIImage *image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, finished, self, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
		if (image) {
			// We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding.
			[self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
		}
	}];
}

不然,在- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;(后边有)办法里解码:

if (imageData && self.decryptor) {
	imageData = [self.decryptor decryptedDataWithData:imageData response:self.response];
}
// 当 dataTask 接纳完一切数据后,session会调用该办法,首要是避免缓存指定的URL或修正与 NSCacheURLResponse 相相关的字典userInfo
// 假如没有完结该办法,那么运用 configuration 决议缓存战略
// proposedResponse 默许的缓存行为;依据当时缓存战略和呼应头的某些字段,如 Pragma 和 Cache-Control 确定
// completionHandler 缓存数据;传递 nil 不做缓存
// 不应该依靠该办法来接纳数据,只要 NSURLRequest.cachePolicy 决议缓存 response 时分调用该办法:
// 只要当以下一切条件都成立时,才会缓存 responses:
// 是HTTP或HTTPS恳求,或许自界说的支撑缓存的网络协议;
// 保证恳求成功,呼应头的状况码在200-299范围内
// response 是来自服务器的,而非缓存中本身就有的
// NSURLRequest.cachePolicy 答应缓存
// NSURLSessionConfiguration.requestCachePolicy 答应缓存
// 呼应头的某些字段 如 Pragma 和 Cache-Control 答应缓存
// response 不能比供给的缓存空间大太多,如不能比供给的磁盘缓存空间还要大5%
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
                                  willCacheResponse:(NSCachedURLResponse *)proposedResponse
                                  completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler;

NSURLSessionTaskDelegateNSURLSessionTask 等级的委托。

// 已完结传输数据的使命,调用该办法
// error 客户端过错,例如无法解析主机名或连接到主机;
// 服务器过错不会在此处显现;
// 为 nil 表明没有发生过错,此使命已成功完结
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;

在这个署理办法里,无error,则进行一系列判别,也会对接受的imageData运用SDImageLoader署理的SDImageLoaderDecodeImageData()办法解码。

咱们就明白了,实践的下载进程是在这儿完结的,仅仅经过一层层的调用,躲藏得太好了。首要进程总结为:

  1. 下载的图片会经过didReceiveData函数不断回来图片data数据,将其拼接到imageData中;
  2. 若设置了图片按SDWebImageDownloaderProgressiveDownload渐进式显现,则会将当时图片解压并显现到屏幕上;
  3. 这个图片下载完结时,调用didCompleteWithError函数,此刻将生成的图片解压缩并设置到屏幕上;

在持续进行之前,咱们来浅看一下SDImageLoaderDecodeImageData()办法,它界说在SDImageLoader文件里,经过一系列判别后,他调用了SDImageIOCoder.m里的- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options;。观察一下SDImageIOCoder类,发现这个是编解码器类,供给了对PNG、JPEG、TIFF类型图片的编解码,还支撑逐行解码。重要的是里边有对SDWebImageProgressiveCoder协议办法的完结。首要有解码、编码、解压和缩小的功用,下面咱们就梳理一下各个功用的逻辑:

解码 这个功用完结比较简略,首要是运用了UIImage类的initWithData:和initWithCGImage: scale: orientation:这两个目标办法:

  • 首要判别是否有图画数据;
  • 接着运用图画数据生成图片目标;
  • 然后判别图片目标是否生成成功;
  • 再获取图画方向,假如方向不是向上,就依据方向生成图片目标;
  • 最终回来生成的图片目标

编码 这个功用触及到了对ImageIO库中CGImageDestinationRef的运用,首要是三个过程:

  1. 运用CGImageDestinationCreateWithData()函数创立图画意图地
  2. 运用CGImageDestinationAddImage()函数将图片目标增加到图画意图地中
  3. 调用CGImageDestinationFinalize()函数将图片目标写入到数据目标中

详细的逻辑是:

  • 首要判别是否有图片目标;
  • 接着判别图片目标的格局;
  • 然后创立图画意图地目标;
  • 接着装备编码的特点,即图画的方向;
  • 然后将图片目标和特点增加到图画意图地目标中编码;
  • 最终回来编码后的数据;

解压 首要过程是:

  1. 运用CGBitmapContextCreate()函数创立位图图画上下文;
  2. 运用CGContextDrawImage()函数将图片制作到位图图画上下文中;
  3. 调用CGBitmapContextCreateImage()函数从位图图画上下文中获取位图图画;

详细逻辑是:

  • 首要判别是否应该解码,假如图片目标不存在、是动图、有透明度就不解码;
  • 接下来创立一个主动开释池,以便在内存不足时铲除缓存;
  • 然后创立一个位图图画上下文;
  • 接着就把图画制作到位图图画上下文中;
  • 再从位图图画上下文中获取制作好的位图目标;
  • 最终运用位图目标生成图片目标并回来;

解压并缩小 详细逻辑:

  • 首要判别是否应该解码,假如图片目标不存在、是动图、有透明度就不解码;
  • 然后判别是否应该缩小,假如图画的像素数量不超过指定数量就只解压不缩小;
  • 接下来创立一个主动开释池,以便在内存不足时铲除缓存;
  • 接着创立一个位图图画上下文;
  • 然后创立一个循环,顺次从原图画中获取一片宽度和原图相等,高度是指定数值,横坐标为0,纵坐标递增的图画,制作到位图图画上下文的指定位置和巨细,直至获取完原图。
  • 再从位图图画上下文中获取制作好的位图目标;
  • 最终运用位图目标生成图片目标并回来;

其间缩小的功用完结的思想是,将原图分片缩小。每次获取原图中的一片进行缩小,直至悉数缩小完结。 参阅博客:源码阅览:SDWebImage(七)——SDWebImageImageIOCoder

接下来再看看SDImageCacheSDImageCache 这个类是SDWebImage中担任缓存相关功用的类。功用包括保存、查询、删去等相关操作,逻辑很明晰。查看注释:SDImageCache 保护内存缓存和磁盘缓存。磁盘缓存写入操作是异步履行的,因而不会给 UI 增加不用要的推迟。这个类还有个私有类SDMemoryCache,它承继自自NSCache,担任办理图画的内存缓存,其内部便是将 NSCache 扩展为了 SDMemoryCache 协议,并为iOS/tvOS渠道增加了@property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; // strong-weak cache,并为其增加了信号量锁来保证线程安全。 weakCache 的作用在于恢复缓存,它经过 CacheConfigshouldUseWeakMemoryCache 开关以操控。先看看其怎么完结的:

- (id)objectForKey:(id)key {
    id obj = [super objectForKey:key];
    if (!self.config.shouldUseWeakMemoryCache) {
        return obj;
    }
    if (key && !obj) {
        // Check weak cache
        SD_LOCK(self.weakCacheLock);
        obj = [self.weakCache objectForKey:key];
        SD_UNLOCK(self.weakCacheLock);
        if (obj) {
            // Sync cache
            NSUInteger cost = 0;
            if ([obj isKindOfClass:[UIImage class]]) {
                cost = [(UIImage *)obj sd_memoryCost];
            }
            [super setObject:obj forKey:key cost:cost];
        }
    }
    return obj;
}

由于 NSCache 遵从 NSDiscardableContent 战略来存储暂时目标的,当内存严重时,缓存目标有或许被体系整理掉。此刻,假如运用拜访 MemoryCache 时,缓存一旦未射中,则会转入 diskCache 的查询操作,或许导致 image 闪耀现象。而当敞开 shouldUseWeakMemoryCache 时,由于 weakCache 保存着目标的弱引用 (在目标 被 NSCache 被整理且没有被开释的状况下),咱们可经过 weakCache 取到缓存,将其放回 NSCache 中。然后减少磁盘 I/O。 SDImageCache完结了:

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
          toMemory:(BOOL)toMemory
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;

办法。该办法将图画异步缓存到指定密钥下的内存中,能够挑选是否缓存到磁盘中,而且能够直接将图画的数据保存到磁盘中,就不用再经过将原图画编码后获取图画的数据再保存到磁盘中,以节约硬件资源。在SDImageDefine.h文件中界说了缓存类型:

// Image Cache Type
typedef NS_ENUM(NSInteger, SDImageCacheType) {
    /**
     * For query and contains op in response, means the image isn't available in the image cache
     * For op in request, this type is not available and take no effect.
     */
    /*
     关于查询并在呼应中包括 op,表明图画在图画缓存中不可用。
     关于恳求中的操作,此类型不可用且无效。
     */
    SDImageCacheTypeNone,
    /**
     * For query and contains op in response, means the image was obtained from the disk cache.
     * For op in request, means process only disk cache.
     */
    /*
     关于查询和包括 op 的呼应,表明映像是从磁盘缓存中获取的。
     关于恳求中的操作,表明仅处理磁盘缓存。
     */
    SDImageCacheTypeDisk,
    /**
     * For query and contains op in response, means the image was obtained from the memory cache.
     * For op in request, means process only memory cache.
     */
    /*
     关于查询和包括 op 的呼应,表明图画是从内存缓存中获取的。
     关于恳求中的 op,表明仅处理内存缓存。
     */
    SDImageCacheTypeMemory,
    /**
     * For query and contains op in response, this type is not available and take no effect.
     * For op in request, means process both memory cache and disk cache.
     */
    /*
     关于查询和包括 op 的呼应,此类型不可用且不起作用。
     关于恳求中的 op,表明处理内存缓存和磁盘缓存。
     */
    SDImageCacheTypeAll
};

进行 memoryCache 写入:

// 假如需求缓存到内存
if (toMemory && self.config.shouldCacheImagesInMemory) {
	NSUInteger cost = image.sd_memoryCost;
	[self.memoryCache setObject:image forKey:key cost:cost];
}

进行 diskCache 写入,操作逻辑放入 ioQueueautoreleasepool 中:

dispatch_async(self.ioQueue, ^{
    @autoreleasepool {
        NSData *data = ... // 依据 SDImageFormat 对 image 进行编码获取
        if (!data && image) {
        	//Check image's associated image format, may return .undefined
        	//...
        	data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil];
        }
        [self _storeImageDataToDisk:data forKey:key];
        if (image) {
            // Check extended data
            id extendedObject = image.sd_extendedObject;
            // ... get extended data
            [self.diskCache setExtendedData:extendedData forKey:key];
        }
    }
    // call completionBlock in main queue
});

另一个重要的办法便是 image query,界说在 SDImageCache 协议中:

- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
    SDImageCacheOptions cacheOptions = 0;
    if (options & SDWebImageQueryMemoryData) cacheOptions |= SDImageCacheQueryMemoryData;
    if (options & SDWebImageQueryMemoryDataSync) cacheOptions |= SDImageCacheQueryMemoryDataSync;
    if (options & SDWebImageQueryDiskDataSync) cacheOptions |= SDImageCacheQueryDiskDataSync;
    if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
    if (options & SDWebImageAvoidDecodeImage) cacheOptions |= SDImageCacheAvoidDecodeImage;
    if (options & SDWebImageDecodeFirstFrameOnly) cacheOptions |= SDImageCacheDecodeFirstFrameOnly;
    if (options & SDWebImagePreloadAllFrames) cacheOptions |= SDImageCachePreloadAllFrames;
    if (options & SDWebImageMatchAnimatedImageClass) cacheOptions |= SDImageCacheMatchAnimatedImageClass;
    return [self queryCacheOperationForKey:key options:cacheOptions context:context cacheType:cacheType done:completionBlock];
}

它只做了一件工作,将 SDWebImageOptions 转化为 SDImageCacheOptions,然后调用 queryCacheOperationForKey: SDImageCacheDefine.m文件中完结了图片的解码办法:

UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context);

内存缓存: SDMemoryCache承继自NSCache。内存缓存的处理是运用 NSCache 目标来完结的。NSCache 是一个类似于调集的容器。它存储 key-value 对,这一点类似于 NSDictionary 类。 咱们通常用运用缓存来暂时存储短时刻运用但创立昂贵的目标。 重用这些目标能够优化功能,由于它们的值不需求从头核算。别的一方面,这些目标关于程序来说不是重要的,在内存严重时会被丢掉。 磁盘缓存: 查看了SDDiskCache文件,它是磁盘缓存类,承继自NSObject,它经过@property (nonatomic, strong, nonnull) NSFileManager *fileManager;一个NSFileManager目标来完结缓存操作。SDImageCache 供给了很多办法来缓存、获取、移除及清空图片。而关于每个图片,为了方便地在内存或磁盘中对它进行这些操作,咱们需求一个 key 值来索引它。 在内存中,咱们将其作为 NSCachekey 值,而在磁盘中,咱们用这个 key 作为图片的文件名。 关于一个长途服务器下载的图片,其 url 是作为这个 key 的最佳挑选了。浅看一下过期缓存的整理:

  1. 依据 SDImageCacheConfigExpireType 排序得到 NSDirectoryEnumerator *fileEnumerator ,开端过滤;
  2. cacheConfig.maxDiskAage 比照判别是否过期,将过期 URL 存入 urlsToDelete
  3. 调用 [self.fileManager removeItemAtURL:fileURL error:nil];
  4. 依据 cacheConfig.maxDiskSize 来删去磁盘缓存的数据,整理到 maxDiskSize 的 1/2 为止。

磁盘查询:

- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
    if (!key) {
        return nil;
    }
    NSData *data = [self.diskCache dataForKey:key];
    if (data) {
        return data;
    }
    // Addtional cache path for custom pre-load cache
    if (self.additionalCachePathBlock) {
        NSString *filePath = self.additionalCachePathBlock(key);
        if (filePath) {
            data = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
        }
    }
    return data;
}
  • 按图片的名称key拼接上界说在存储途径下的全途径,依照全途径在磁盘中查找是否存在图片;
  • 除了生成存储的途径外,咱们还能够自界说自己的存放途径到磁盘中,依照当时图片的名称key拼接上自界说途径到磁盘中查找;
  • 若查找到的话,是一个NSData类型的图片数据,需转为UIImage目标之后,需将其解压到屏幕上显现。

小结:

SDWebImage经过了一系列复杂的封装,把一个繁琐的进程封装成简略的接口来运用,完结了并发的图片下载及解压缩操作以及内存与磁盘存储等复杂功用。从网上看到下面这张图片,觉得特别好,非常明晰:

[iOS开发]SDWebImage源码学习