SDWebImage主线梳理(一)

本文只梳理SDWebImage主线相关的内容,所谓的主线就是从UIImageView调用sd_setImageWithURL:方法开始,梳理这一条主线上的相关内容。

本文使用的版本是 pod 'SDWebImage', '~> 5.3.1'

先看一下 SDWebImage 在 GitHub 上给出的时序图,SDWebImage的整体调用顺序就如下图所示:

Sequence.png

由于SDWebImage架构体系复杂,因此会由浅入深分成几部分对主线内容进行解析。文章篇幅太长受限,不得不分割成两篇。第二篇传送门:SDWebImage主线梳理(二)

  1. 第一部分:基础源码的解析(主线正向流程)
  2. 第二部分:主线涉及的重要block的时间线梳理(主线逆向流程)
  3. 第三部分:本人在阅读源码时遇到的问题
  4. 第四部分:SDWebImage小知识点(纯属个人喜好)
  5. 另外,解码部分和缓存部分将会分别单独各写一篇文章进行解析

为了方便表述和定位,重点解析的方法都会先在源码的注释中标注序号,按照序号去找对应的解析即可。


第一部分:基础源码解析(主线正向流程)

主线正向流程(我自己起的名)指的是从一个UIImageView调用sd_setImageWithURL:命令开始,一直到SD开启了下载任务(网络请求尚未回调)为止,这一段的主要流程。

1-1

废话不多说,直接开始

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self.imageView sd_setImageWithURL:YourImageURL];
}

imageView调用 sd_setImageWithURL:方法,开启SDWebImage之旅。YourImageURL 只要是个网络图片URL即可,随意指定。

1-2

来到-[UIImageView(WebCache) sd_setImageWithURL:]

- (void)sd_setImageWithURL:(nullable NSURL *)url {
    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}

连锁调用,继续

1-3

来到-[UIImageView(WebCache) sd_setImageWithURL:placeholderImage:options:progress:completed:]

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

继续

1-4 触发主线方法

来到-[UIImageView(WebCache) sd_setImageWithURL:placeholderImage:options:context:progress:completed:]

- (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);
                               }
                           }];
}

虽然还是连锁调用,但是有两点要注意:

  1. 这里实现了sd_internalSetImageWithURL:...方法的completedBlock, 这个block的类型是SDInternalCompletionBlock,为了方便第二部分追踪block的调用顺序,我们给它起个名就叫 InternalBlock1
  2. InternalBlock1 的实现里调用了 -[UIImageView(WebCache) sd_setImageWithURL...]方法的completedBlock,这个block是要回调给开发者的,所以不必深究。


1-5 主线方法

-[UIView(WebCache) sd_internalSetImageWithURL:placeholderImage:options:context:setImageBlock:progress:completed:]

约定一下block的简称:

  1. 方法中定义并实现了 combinedProgressBlockSDImageLoaderProgressBlock类型。这个就是进度回调的block,以下简称 ProgressBlock
  2. 方法中还实现了 loadImageWithURL:options:context:progress:completed: 方法的completedBlock,SDInternalCompletionBlock类型。以下简称 InternalBlock2

由于这个方法源码量稍有些多,所以最直接的解析内容先写在源码的注释中。细化的内容在后面按照编号顺序进行解析。

- (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 {
    // 这个context是个nil,是在连锁调用中补上的一个参数
    context = [context copy]; // copy to avoid mutable object
    // context是nil肯定取不出值来,此处validOperationKey仍然是nil
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
    if (!validOperationKey) {
        // 相当于 validOperationKey = @"UIImageView"
        validOperationKey = NSStringFromClass([self class]);
    }
    // sd_latestOperationKey是一个关联属性,关联属性的 key = @selector(sd_latestOperationKey); 详见 1-5-1
    self.sd_latestOperationKey = validOperationKey;
    // 取消所有队列中的下载任务; 详见 1-5-2
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    // sd_imageURL也是一个关联属性,key = @selector(sd_imageURL); 详见 1-5-3
    self.sd_imageURL = url;
    // 设置占位图的枚举值; 详见1-5-4
    if (!(options & SDWebImageDelayPlaceholder)) {
        // dispatch_main_async_safe() 函数是SDWebImage自定义的,保证代码在主线程执行; 详见1-5-5
        dispatch_main_async_safe(^{
            // 为UIImageView设置图片(此处是设置占位图)
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        });
    }
    // 一般情况url都是我们传的图片地址,不会空,所以主要看if分支,忽略else分支
    if (url) {
        // reset the progress
        // 下载进度归零(或称重置、亦或复位)
        NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
        if (imageProgress) {
            imageProgress.totalUnitCount = 0;
            imageProgress.completedUnitCount = 0;
        }
// SD_UIKIT 是 SDWebImage 自定义的宏,如果当前系统是iOS或tvOS则为真
#if SD_UIKIT || SD_MAC
        // check and start image indicator
        // 大概是菊花转圈圈之类的,这个东西不多逼逼
        [self sd_startImageIndicator];
        id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
        // 和前面的validOperationKey一样,取出来都是nil
        SDWebImageManager *manager = context[SDWebImageContextCustomManager];
        if (!manager) {
            // 获取SDWebImageManager单例对象; 详见1-5-6
            manager = [SDWebImageManager sharedManager];
        }
        // combinedProgressBlock 先保存一段代码暂不执行
        SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
            if (imageProgress) {
                // 首先是修改imageProgress的总数和已完成数
                imageProgress.totalUnitCount = expectedSize;
                imageProgress.completedUnitCount = receivedSize;
            }
#if SD_UIKIT || SD_MAC
            if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) {
                // 利用 receivedSize 和 expectedSize 计算当前进度progress(0-1之间)
                double progress = 0;
                if (expectedSize != 0) {
                    progress = (double)receivedSize / expectedSize;
                }
                // MAX(MIN(progress, 1), 0) 这个写法很有意思,保证无论你输入什么值最后范围都在0-1之间
                progress = MAX(MIN(progress, 1), 0); // 0.0 - 1.0
                dispatch_async(dispatch_get_main_queue(), ^{
                    // 更新 imageIndicator 的状态
                    [imageIndicator updateIndicatorProgress:progress];
                });
            }
#endif
            // 如果开发者需要(实现了block),把进度回调给开发者
            if (progressBlock) {
                progressBlock(receivedSize, expectedSize, targetURL);
            }
        };
        @weakify(self);
        // loadImageWithURL: 方法是主线方法,详见 1-5-7
        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
            // 如果finished为真,结束indicator
            if (finished) {
                [self sd_stopImageIndicator];
            }
#endif
            // 如果finished为真或者选项为SDWebImageAvoidAutoSetImage(避免自动设置图片),那么应该调用CompletedBlock
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            // 跟下面那段英文说的一个意思:有图(image)但是选项是SDWebImageAvoidAutoSetImage,
            // 或者没图但是选项没选SDWebImageDelayPlaceholder(图片网络请求完成后再设置占位图),这两种情况都不该设置图片(主图和占位图都不设置)
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
                if (!self) { return; }
                if (!shouldNotSetImage) {
                    [self sd_setNeedsLayout];
                }
                // 这个 completedBlock 就是前面 1-4 提到的 InternalBlock1
                if (completedBlock && shouldCallCompletedBlock) {
                    completedBlock(image, data, error, cacheType, finished, url);
                }
            };
            
            // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
            // OR
            // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
            if (shouldNotSetImage) {
                // 这里调用 callCompletedBlockClojure block,将会间接触发 InternalBlock1
                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;
            if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
                transition = self.sd_imageTransition;
            }
#endif
            dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
                // 给imageView设置图片; 详见1-5-8
                [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 block,将会间接触发 InternalBlock1
                callCompletedBlockClojure();
            });
        }];
        // 取消并删除旧的operation,保存新的operation(本方法中loadImageWithURL:方法返回的); 详见 1-5-9
        [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);
            }
        });
    }
}


1-5-1

sd_latestOperationKey是分类中定义的一个关联属性,没有声明,只有GETTER和SETTER。

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


1-5-2

-[UIView(WebCacheOperation) sd_cancelImageLoadOperationWithKey:] 取消所有队列中的下载任务

- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    if (key) {
        // Cancel in progress downloader from queue
        // 从sd_operationDictionary中获取操作字典; sd_operationDictionary是一个关联属性,详见1-5-2-1
        SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
        // 定义一个遵守SDWebImageOperation协议的指针
        id<SDWebImageOperation> operation;
        // 读操作增加线程锁        
        @synchronized (self) {
            operation = [operationDictionary objectForKey:key];
        }
        if (operation) {
            if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
                // 取消操作
                [operation cancel];
            }
            @synchronized (self) {
                // 从操作字典中移除
                [operationDictionary removeObjectForKey:key];
            }
        }
    }
}

1-5-2-1

sd_operationDictionary是分类中定义的一个关联属性,没有声明和SETTER,只有GETTER。

- (SDOperationsDictionary *)sd_operationDictionary {
    // 全程加锁
    @synchronized(self) {
        // 获取操作字典,SDOperationsDictionary类型
        SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
        // 之前保存过就直接返回
        if (operations) {
            return operations;
        }
        // 之前没保存过就新创建一个 NSMapTable,关于 NSMapTable 详见1-5-2-1-1
        operations = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
        // 保存到关联属性
        objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        return operations;
    }
}

值得一提的是这个关联属性是SDOperationsDictionary类型。SDOperationsDictionary类型是SD自定义(typedef)的类型。表面上看是一个字典(NSDictionary),其实是NSMapTable类型

// key is strong, value is weak because operation instance is retained by SDWebImageManager's runningOperations property
// we should use lock to keep thread-safe because these method may not be acessed from main queue
typedef NSMapTable<NSString *, id<SDWebImageOperation>> SDOperationsDictionary;

1-5-2-1-1

关于 NSMapTable

  1. 首先 NSMapTable 也是一种映射表。
  2. 相对 NSSet 和 NSDictionary 而言,NSMapTable 可以自由定义是否强引用key、value。而NSSet和NSDictionary默认是强引用key、value。
  3. NSMapTable 的 key、value更加自由,可以是对象到对象,也可以是C指针到对象等等

Q1: 为什么使用 NSMapTable 存储 operation<SDWebImageOperation> 实例 ?
A1: 在此例中,NSMapTable 强引用 key,弱引用 value。因为operation实例被SDWebImageManager的runningOperations属性持有了,我们应该使用lock来保持线程安全,这些方法可能不会从主队列访问。

1-5-3

sd_imageURL 虽然在分类的头文件中有声明,但是GETTER和SETTER都是使用关联属性进行的管理。说句题外话,由于 sd_imageURL 同时实现了GETTER和SETTER,系统就不会自动生成成员变量了。

/**
 * Get the current image URL.
 *
 * @note Note that because of the limitations of categories this property can get out of sync if you use setImage: directly.
 */
@property (nonatomic, strong, readonly, nullable) NSURL *sd_imageURL;

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


1-5-4

SDWebImageDelayPlaceholder 是 SDWebImageOptions 类型的枚举值

选项用途:图片网络请求或者从缓存加载完成后再设置占位图。实际上就是把占位图作为最后的希望,如果缓存查找失败并且网络下载也失败,那么会把占位图派上场,打替补。

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
...

    /**
     * By default, placeholder images are loaded while the image is loading. This flag will delay the loading
     * of the placeholder image until after the image has finished loading.
     * 默认情况下,占位图在图片加载(网络请求或从缓存加载)过程中就被加载(展示到imageView)了。这个选项将会延迟占
       位图加载(展示到imageView),直到图片加载(网络请求)完成后。
     */
    SDWebImageDelayPlaceholder = 1 << 8,

...
}


1-5-5

dispatch_main_async_safe() 函数是SDWebImage自定义的宏,保证代码在主线程执行。

  1. 如果当前队列是主队列(在主线程中),则直接回调block,也就是顺序执行代码,相当于dispatch_main_async_safe()什么都没做。
  2. 如果当前队列不是主队列(有可能是在子线程),则异步拉回主队列回调block,保证代码在主队列(主线程)执行。
#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
    NSLog(@"%s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));\
    if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {\
        NSLog(@"%s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }
#endif

1-5-6  大量代码来袭

+[SDWebImageManager sharedManager] 获取 SDWebImageManager 单例对象

+ (nonnull instancetype)sharedManager {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new]; // 相当于调用 init
    });
    return instance;
}

-[SDWebImageManager init] 名义上的 init,其实内部并没有初始化,还需要调用下一方法进行初始化。

- (nonnull instancetype)init {
    // 取出默认图片缓存,默认为nil; defaultImageCache 是 SDWebImageManager 的类属性,详见1-5-6-1
    id<SDImageCache> cache = [[self class] defaultImageCache];
    if (!cache) {
        // 没有 defaultImageCache 则使用 sharedImageCache; sharedImageCache 详见1-5-6-2
        cache = [SDImageCache sharedImageCache];
    }
    // 取出默认图片加载器,默认为nil; defaultImageLoader 是 SDWebImageManager 的类属性,详见1-5-6-3
    id<SDImageLoader> loader = [[self class] defaultImageLoader];
    if (!loader) {
        // 没有 defaultImageLoader 则使用 sharedDownloader; sharedDownloader 详见1-5-6-4
        loader = [SDWebImageDownloader sharedDownloader];
    }
    // -[SDWebImageManager initWithCache:loader:]
    return [self initWithCache:cache loader:loader];
}

-[SDWebImageManager initWithCache:loader:] 没什么可说的,就是各种保存。

- (nonnull instancetype)initWithCache:(nonnull id<SDImageCache>)cache loader:(nonnull id<SDImageLoader>)loader {
    if ((self = [super init])) {
        _imageCache = cache;
        _imageLoader = loader;
        _failedURLs = [NSMutableSet new];
        _failedURLsLock = dispatch_semaphore_create(1);
        _runningOperations = [NSMutableSet new];
        _runningOperationsLock = dispatch_semaphore_create(1);
    }
    return self;
}

1-5-6-1

SDWebImageManager 的类属性 defaultImageCache,默认为nil,也就是默认使用 sharedImageCache 作为 image cache。

/**
 The default image cache when the manager which is created with no arguments. Such as shared manager or init.
 Defaults to nil. Means using `SDImageCache.sharedImageCache`
 */
@property (nonatomic, class, nullable) id<SDImageCache> defaultImageCache;

无论SETTER还是GETTER都是在操作全局静态变量 _defaultImageCache,由于 defaultImageCache 是类属性,所以GETTER和SETTER都需要自己实现。

static id<SDImageCache> _defaultImageCache;

...

+ (id<SDImageCache>)defaultImageCache {
    return _defaultImageCache;
}

+ (void)setDefaultImageCache:(id<SDImageCache>)defaultImageCache {
    if (defaultImageCache && ![defaultImageCache conformsToProtocol:@protocol(SDImageCache)]) {
        return;
    }
    _defaultImageCache = defaultImageCache;
}

1-5-6-2

+[SDImageCache sharedImageCache] 获取 SDImageCache 单例对象

+ (nonnull instancetype)sharedImageCache {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new]; // 相当于调用 init
    });
    return instance;
}

-[SDImageCache init] 传入参数 namespace = @"default"

- (instancetype)init {
    return [self initWithNamespace:@"default"];
}

-[SDImageCache initWithNamespace:]

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns {
    return [self initWithNamespace:ns diskCacheDirectory:nil];
}

-[SDImageCache initWithNamespace:diskCacheDirectory:]

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nullable NSString *)directory {
    return [self initWithNamespace:ns diskCacheDirectory:directory config:SDImageCacheConfig.defaultCacheConfig];
}

-[SDImageCache initWithNamespace:diskCacheDirectory:config:]

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nullable NSString *)directory
                                   config:(nullable SDImageCacheConfig *)config {
    if ((self = [super init])) {
        NSAssert(ns, @"Cache namespace should not be nil");
        
        // Create IO serial queue
        // 创建一个串行队列
        _ioQueue = dispatch_queue_create("com.hackemist.SDImageCache", DISPATCH_QUEUE_SERIAL);
        // 如果入参config为空,则取 SDImageCacheConfig 单例对象; 详见1-5-6-2-1
        if (!config) {
            config = SDImageCacheConfig.defaultCacheConfig;
        }
        _config = [config copy];
        
        // Init the memory cache
        NSAssert([config.memoryCacheClass conformsToProtocol:@protocol(SDMemoryCache)], @"Custom memory cache class must conform to `SDMemoryCache` protocol");
        // 初始化一个内存缓存实例; 缓存部分会在另外一篇文章详细解析。
        _memoryCache = [[config.memoryCacheClass alloc] initWithConfig:_config];
        
        // Init the disk cache
        // 默认 directory 为 nil
        if (directory != nil) {
             // directory如果不为空,拼接一个缓存文件路径
            _diskCachePath = [directory stringByAppendingPathComponent:ns];
        } else {
            // 拼接一个缓存路径,userCacheDirectory 方法仅仅是获取系统的缓存文件夹路径;
            // 这个 path 与 migrateDiskCacheDirectory 方法中的新路径规则一致
            NSString *path = [[[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:ns];
            _diskCachePath = path;
        }
        
        NSAssert([config.diskCacheClass conformsToProtocol:@protocol(SDDiskCache)], @"Custom disk cache class must conform to `SDDiskCache` protocol");
        // 初始化一个磁盘缓存实例; 缓存部分会在另外一篇文章详细解析。
        _diskCache = [[config.diskCacheClass alloc] initWithCachePath:_diskCachePath config:_config];
        
        // Check and migrate disk cache directory if need
        // 把旧文件夹的缓存移动到新文件夹路径下; 详见1-5-6-2-2
        [self migrateDiskCacheDirectory];

#if SD_UIKIT
        // Subscribe to app events
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationWillTerminate:)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationDidEnterBackground:)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
#endif
// 这个分支不用看
#if SD_MAC
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationWillTerminate:)
                                                     name:NSApplicationWillTerminateNotification
                                                   object:nil];
#endif
    }

    return self;
}

1-5-6-2-1

+[SDImageCacheConfig defaultCacheConfig] 获取 SDImageCacheConfig 单例对象

+ (SDImageCacheConfig *)defaultCacheConfig {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _defaultCacheConfig = [SDImageCacheConfig new]; // 调用 init
    });
    return _defaultCacheConfig;
}

-[SDImageCacheConfig init]

- (instancetype)init {
    if (self = [super init]) {
        _shouldDisableiCloud = YES;  // 禁用iCloud
        _shouldCacheImagesInMemory = YES;  // 在内存中缓存图片
        _shouldUseWeakMemoryCache = YES;  // 使用弱内存缓存
        _shouldRemoveExpiredDataWhenEnterBackground = YES;  // App进入后台清除过期数据
        _diskCacheReadingOptions = 0;
        _diskCacheWritingOptions = NSDataWritingAtomic;
        _maxDiskAge = kDefaultCacheMaxDiskAge;  // 磁盘缓存最大过期时间,默认为7天
        _maxDiskSize = 0;  // 磁盘缓存可占用的最大空间大小,0 即为不限制
        _diskCacheExpireType = SDImageCacheConfigExpireTypeModificationDate;  // 过期时间类型
        _memoryCacheClass = [SDMemoryCache class];
        _diskCacheClass = [SDDiskCache class];
    }
    return self;
}

1-5-6-2-2

-[SDImageCache migrateDiskCacheDirectory] 把旧文件夹的缓存移动到新文件夹路径下

- (void)migrateDiskCacheDirectory {
    if ([self.diskCache isKindOfClass:[SDDiskCache class]]) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            // 使用模拟器时新路径长这样:~/Library/Caches/com.hackemist.SDImageCache/default/
            NSString *newDefaultPath = [[[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:@"default"];
            // 使用模拟器时旧路径长这样:~/Library/Caches/default/com.hackemist.SDWebImageCache.default/
            NSString *oldDefaultPath = [[[self userCacheDirectory] stringByAppendingPathComponent:@"default"] stringByAppendingPathComponent:@"com.hackemist.SDWebImageCache.default"];
            dispatch_async(self.ioQueue, ^{
                [((SDDiskCache *)self.diskCache) moveCacheDirectoryFromPath:oldDefaultPath toPath:newDefaultPath];
            });
        });
    }
}

Q: 为什么要移动?
A: 没有在官方的文档中找到解释。个人猜测,应该是为了兼容旧版本的SD的缓存路径。如果哪位大牛知道请不吝留言赐教!

1-5-6-3

SDWebImageManager 的类属性 defaultImageLoader,默认为nil,也就是默认使用 sharedDownloader 作为 image loader。

/**
 The default image loader for manager which is created with no arguments. Such as shared manager or init.
 Defaults to nil. Means using `SDWebImageDownloader.sharedDownloader`
 */
@property (nonatomic, class, nullable) id<SDImageLoader> defaultImageLoader;

无论SETTER还是GETTER都是在操作全局静态变量 _defaultImageLoader,由于 defaultImageLoader 是类属性,所以GETTER和SETTER都需要自己实现。

static id<SDImageLoader> _defaultImageLoader;

...

+ (id<SDImageLoader>)defaultImageLoader {
    return _defaultImageLoader;
}

+ (void)setDefaultImageLoader:(id<SDImageLoader>)defaultImageLoader {
    if (defaultImageLoader && ![defaultImageLoader conformsToProtocol:@protocol(SDImageLoader)]) {
        return;
    }
    _defaultImageLoader = defaultImageLoader;
}

1-5-6-4

+[SDWebImageDownloader sharedDownloader] 获取 SDWebImageDownloader 单例对象

+ (nonnull instancetype)sharedDownloader {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new]; // 调用init
    });
    return instance;
}

-[SDWebImageDownloader init]

- (nonnull instancetype)init {
    return [self initWithConfig:SDWebImageDownloaderConfig.defaultDownloaderConfig];
}

-[SDWebImageDownloader initWithConfig:]

- (instancetype)initWithConfig:(SDWebImageDownloaderConfig *)config {
    self = [super init];
    if (self) {
        // 入参 config 在 -[SDWebImageDownloader init] 方法中就被赋值 SDWebImageDownloaderConfig.defaultDownloaderConfig
        if (!config) {
            config = SDWebImageDownloaderConfig.defaultDownloaderConfig;
        }
        _config = [config copy];
        [_config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxConcurrentDownloads)) options:0 context:SDWebImageDownloaderContext];
        // 创建一个 NSOperationQueue
        _downloadQueue = [NSOperationQueue new];
        // 最大并发数,默认为 6
        _downloadQueue.maxConcurrentOperationCount = _config.maxConcurrentDownloads;
        _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
        _URLOperations = [NSMutableDictionary new];
        NSMutableDictionary<NSString *, NSString *> *headerDictionary = [NSMutableDictionary dictionary];
        NSString *userAgent = nil;
#if SD_UIKIT
        // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
        // 按照标准配置 User-Agent
        userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif SD_WATCH
        // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
        userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif SD_MAC
        userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
        if (userAgent) {
            // 如果userAgent不能被转换成ASCII编码格式,处理userAgent为可变字符串。
            if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
                NSMutableString *mutableUserAgent = [userAgent mutableCopy];
                if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
                    userAgent = mutableUserAgent;
                }
            }
            headerDictionary[@"User-Agent"] = userAgent;
        }
        headerDictionary[@"Accept"] = @"image/*,*/*;q=0.8";
        // headerDictionary 中现在有两个字段:"User-Agent" 以及 "Accept"
        _HTTPHeaders = headerDictionary;
        _HTTPHeadersLock = dispatch_semaphore_create(1);
        _operationsLock = dispatch_semaphore_create(1);
        NSURLSessionConfiguration *sessionConfiguration = _config.sessionConfiguration;
        if (!sessionConfiguration) {
            sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
        }
        /**
         *  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.
         */
        // 创建 NSURLSession ,代理是 SDWebImageDownloader 单例对象(自己)
        _session = [NSURLSession sessionWithConfiguration:sessionConfiguration
                                                 delegate:self
                                            delegateQueue:nil];
    }
    return self;
}


1-5-7 主线方法-加载图片

-[SDWebImageManager loadImageWithURL:options:context:progress:completed:]

- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                          options:(SDWebImageOptions)options
                                          context:(nullable SDWebImageContext *)context
                                         progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                        completed:(nonnull SDInternalCompletionBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    // 断言:必须传入 completedBlock(必须实现了此block),调用此方法不传completedBlock是无意义的
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
    // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
    // 处理入参 url,如果传入的是 NSString 则转换成 NSURL
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    // 再次处理入参 url,如果传入的 url 不是 NSURL 则将 url 置为空
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }
    // 创建 SDWebImageCombinedOperation 实例
    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    // manager 是 operation 的私有属性,所有权修饰符为 weak,弱引用
    operation.manager = self;

    BOOL isFailedUrl = NO;
    if (url) {
        // 使用信号量加锁
        SD_LOCK(self.failedURLsLock);
        // 如果 failedURLs(NSSet) 包含此 url,则临时变量 isFailedUrl 将被赋值 YES
        isFailedUrl = [self.failedURLs containsObject:url];
        SD_UNLOCK(self.failedURLsLock);
    }
    // 如果url字符长度为零,或者options不是失败重试(SDWebImageRetryFailed)并且这个url失败过
    // SDWebImageRetryFailed 是一个枚举值,详见1-5-7-1
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        // 触发 InternalBlock2 回调错误信息,详见1-5-7-2
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}] url:url];
        return operation;
    }
    // 使用信号量加锁
    SD_LOCK(self.runningOperationsLock);
    // SDWebImageManager 的属性 runningOperations(NSMutableSet) 持有 operation,对比 1-5-2-1-1
    [self.runningOperations addObject:operation];
    SD_UNLOCK(self.runningOperationsLock);
    
    // Preprocess the options and context arg to decide the final the result for manager
    // 对options和context参数进行预处理,以决定最后的结果; 详见1-5-7-3
    SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
    
    // Start the entry to load image from cache
    // 主线方法! 处理缓存相关业务,此处传递给 callCacheProcessForOperation 方法的 completedBlock 是 InternalBlock2,详见1-5-7-4
    [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];

    return operation;
}

1-5-7-1

SDWebImageRetryFailed 与 1-5-4 中的 SDWebImageDelayPlaceholder 是同一类型枚举值,都定义在 SDWebImageOptions 中。

默认情况下,当一个URL下载失败后,这个URL将被列入黑名单不再重试。这个选项可以禁用这个黑名单。

/// WebCache options
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    /**
     * By default, when a URL fail to be downloaded, the URL is blacklisted so the library won't keep trying.
     * This flag disable this blacklisting.
     */
    SDWebImageRetryFailed = 1 << 0,

...
}

1-5-7-2

-[SDWebImageManager callCompletionBlockForOperation:completion:error:url:] 连锁调用

- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
                             completion:(nullable SDInternalCompletionBlock)completionBlock
                                  error:(nullable NSError *)error
                                    url:(nullable NSURL *)url {
    [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url];
}

-[SDWebImageManager callCompletionBlockForOperation:completion:image:data:error:cacheType:finished:url:]
方法的入参 completionBlock 是 SDInternalCompletionBlock 类型,也就是我们前面简称 InternalBlock2 的block。

- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
                             completion:(nullable SDInternalCompletionBlock)completionBlock
                                  image:(nullable UIImage *)image
                                   data:(nullable NSData *)data
                                  error:(nullable NSError *)error
                              cacheType:(SDImageCacheType)cacheType
                               finished:(BOOL)finished
                                    url:(nullable NSURL *)url {
    // dispatch_main_async_safe() 函数在前面 1-5-5 介绍过
    dispatch_main_async_safe(^{
        if (completionBlock) {
            // 触发 completionBlock,也就是 InternalBlock2
            completionBlock(image, data, error, cacheType, finished, url);
        }
    });
}

1-5-7-3

-[SDWebImageManager processedResultForURL:options:context:]

代码虽然多,但是逻辑很简单,总结如下:

  • 初始化 mutableContext (就是个可变字典)
  • 如果 mutableContext 中没有 transformer、cacheKeyFilter、cacheSerializer,mutableContext 会把 SDWebImageManager (即self) 的拿过来保存一份
  • 将 context 追加到 mutableContext 中,再将新的 mutableContext 的内容 copy 一份赋值给入参 context
  • 初始化一个 SDWebImageOptionsResult 实例并返回
- (SDWebImageOptionsResult *)processedResultForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
    SDWebImageOptionsResult *result;
    SDWebImageMutableContext *mutableContext = [SDWebImageMutableContext dictionary];
    
    // Image Transformer from manager
    if (!context[SDWebImageContextImageTransformer]) {
        id<SDImageTransformer> transformer = self.transformer;
        [mutableContext setValue:transformer forKey:SDWebImageContextImageTransformer];
    }
    // Cache key filter from manager
    if (!context[SDWebImageContextCacheKeyFilter]) {
        id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;
        [mutableContext setValue:cacheKeyFilter forKey:SDWebImageContextCacheKeyFilter];
    }
    // Cache serializer from manager
    if (!context[SDWebImageContextCacheSerializer]) {
        id<SDWebImageCacheSerializer> cacheSerializer = self.cacheSerializer;
        [mutableContext setValue:cacheSerializer forKey:SDWebImageContextCacheSerializer];
    }
    
    if (mutableContext.count > 0) {
        if (context) {
            [mutableContext addEntriesFromDictionary:context];
        }
        context = [mutableContext copy];
    }
    
    // Apply options processor
    // 默认为空
    if (self.optionsProcessor) {
        result = [self.optionsProcessor processedResultForURL:url options:options context:context];
        
    }
    // 初始化一个 SDWebImageOptionsResult 实例
    if (!result) {
        // Use default options result
        // 调用 initWithOptions:context: 方法
        result = [[SDWebImageOptionsResult alloc] initWithOptions:options context:context];
    }
    
    return result;
}

-[SDWebImageOptionsResult initWithOptions:context:]
保存 options 和 context,初始化一个 SDWebImageOptionsResult 实例

- (instancetype)initWithOptions:(SDWebImageOptions)options context:(SDWebImageContext *)context {
    self = [super init];
    if (self) {
        self.options = options;
        self.context = context;
    }
    return self;
}

1-5-7-4 主线方法

-[SDWebImageManager callCacheProcessForOperation:url:options:context:progress:completed:]

  1. 约定一下block简称:

    • 这里实现了queryImageForKey:options:context:completion:方法的completionBlock, SDImageCacheQueryCompletionBlock类型。为了方便,以下简称此block为 doneBlock
  2. 主要是在处理缓存和下载相关业务

    • 首先去查找缓存,在 1-5-7-4-3-1 会有查找结果,无论是否查找到缓存,结束后都会调用 doneBlock 回到这里。
    • 1-5-7-4-3-1 调用 doneBlock 回传image和data,在 doneBlock 的实现代码中调用 callDownloadProcessForOperation 方法进入下载业务模块(1-5-7-4-5)。
// Query 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 {
    // Check whether we should query cache
    // shouldQueryCache 是否应该去查找缓存,如果没有选择 SDWebImageFromLoaderOnly,则为 YES
    // SDWebImageFromLoaderOnly 是一个枚举值,详见1-5-7-4-1
    BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
    if (shouldQueryCache) {
        // 默认 context 中没有 cacheKeyFilter
        id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
        // cacheKeyForURL:cacheKeyFilter: 方法返回 URL 的字符串, 详见1-5-7-4-2
        NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
        @weakify(operation);
        // 主线方法:queryImageForKey: 查找缓存,缓存业务模块的入口, 详见 1-5-7-4-3
        operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
            @strongify(operation);
            // 如果 operation 不存在或者被取消,回调错误信息
            if (!operation || operation.isCancelled) {
                // Image combined operation cancelled by user
                // callCompletionBlockForOperation: 只是单纯的调用 completion: block回调错误信息,详见1-5-7-4-4
                [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil] url:url];
                [self safelyRemoveOperationFromRunning:operation];
                return;
            }
            // Continue download process
            // 主线方法:下载业务模块的入口,需要等待查询缓存之后(等待 queryImageForKey 的回调)决定是否需要下载操作; 详见 1-5-7-4-5
            [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
        }];
    } else {
        // Continue download process
        // 主线方法:下载业务模块的入口,不需要查询缓存时,直接下载; 如果你不选择 SDWebImageFromLoaderOnly 这个选项,不会走这个分支
        [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
    }
}

1-5-7-4-1

SDWebImageFromLoaderOnly 是一个枚举值,对没错,仍然与 1-5-4 中的 SDWebImageDelayPlaceholder 是同一类型枚举值,都定义在 SDWebImageOptions 中。

SDWebImageFromLoaderOnly选项:默认情况下,SD在下载器下载图片完成之前会去查询缓存的图片来用。这个选项会阻止这个默认的操作,只从下载器加载图片。

/// WebCache options
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {

    ...

    /**
     * By default, we query the cache before the image is load from the loader. This flag can prevent this to load from loader only.
     */
    SDWebImageFromLoaderOnly = 1 << 16,

...

1-5-7-4-2

-[SDWebImageManager cacheKeyForURL:cacheKeyFilter:] ,从 NSURL 中提取 URL 字符串。

- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url cacheKeyFilter:(id<SDWebImageCacheKeyFilter>)cacheKeyFilter {
    if (!url) {
        return @"";
    }
    // 默认 cacheKeyFilter 为空
    if (cacheKeyFilter) {
        // 开发者可以自定义处理URL的规则,并返回处理之后的 URL 字符串;详见1-5-7-4-2-1
        return [cacheKeyFilter cacheKeyForURL:url];
    } else {
        // 默认直接返回从 NSURL 中提取的 URL 字符串
        return url.absoluteString;
    }
}

1-5-7-4-2-1

如果开发者不想单纯的直接返回从 NSURL 中提取的 URL 字符串,可以通过 SDWebImageCacheKeyFilter 的初始化方法 -[SDWebImageCacheKeyFilter initWithBlock:]+[SDWebImageCacheKeyFilter cacheKeyFilterWithBlock:] 传入block自定一个规则,也就是你想如何处理NSURL,但最后仍要返回一个 URL 字符串。

-[SDWebImageCacheKeyFilter cacheKeyForURL:]

- (NSString *)cacheKeyForURL:(NSURL *)url {
    // 这个block来自初始化方法 -[SDWebImageCacheKeyFilter initWithBlock:] 或 +[SDWebImageCacheKeyFilter cacheKeyFilterWithBlock:]
    if (!self.block) {
        return nil;
    }
    return self.block(url);
}
// SDWebImageCacheKeyFilterBlock 类型的block传入NSURL,返回NSString
- (instancetype)initWithBlock:(SDWebImageCacheKeyFilterBlock)block {
    self = [super init];
    if (self) {
        self.block = block;
    }
    return self;
}

+ (instancetype)cacheKeyFilterWithBlock:(SDWebImageCacheKeyFilterBlock)block {
    SDWebImageCacheKeyFilter *cacheKeyFilter = [[SDWebImageCacheKeyFilter alloc] initWithBlock:block];
    return cacheKeyFilter;
}

SDWebImageCacheKeyFilterBlock 类型的block传入NSURL,返回NSString

typedef NSString * _Nullable(^SDWebImageCacheKeyFilterBlock)(NSURL * _Nonnull url);

1-5-7-4-3 主线方法

-[SDImageCache queryImageForKey:options:context:completion:]
首先将选项枚举值对应转换,即 SDWebImageOptions 转换成 SDImageCacheOptions 对应的枚举值。然后调用 queryCacheOperationForKey: 方法。

- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context 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;
    // queryCacheOperationForKey:options:context:done: 方法详见 1-5-7-4-3-1
    return [self queryCacheOperationForKey:key options:cacheOptions context:context done:completionBlock];
}

1-5-7-4-3-1 主线方法-查找缓存

-[SDImageCache queryCacheOperationForKey:options:context:done:]

  1. 简单来讲内存缓存(SDMemoryCache)保存的是 UIImage 对象;磁盘缓存(SDDiskCache)保存的是 NSData 二进制数据。
  2. 查找缓存分为两步:第1步 查找内存缓存;第2步 查找磁盘缓存;
  3. 不论最终是否查找到缓存,都要调用 doneBlock ,回到 1-5-7-4
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    // 默认 context 中无 transformer,transformer 为空
    id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
    if (transformer) {
        // grab the transformed disk image if transformer provided
        NSString *transformerKey = [transformer transformerKey];
        // 处理key,经过形变的 key 组装的内容是不同于普通图片名称(key)的
        key = SDTransformedKeyForKey(key, transformerKey);
    }

    // First check the in-memory cache...
    // 第1步 查找内存缓存,详见 1-5-7-4-3-1-1
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    // 如果有内存缓存则走此 if 分支
    if (image) {
        // 如果options是SDImageCacheDecodeFirstFrameOnly(只取动图的第一帧),则走此 if 分支;枚举值详见 1-5-7-4-3-1-2
        if (options & SDImageCacheDecodeFirstFrameOnly) {
            // Ensure static image
            // 获取 image 的类
            Class animatedImageClass = image.class;
            // 判断是不是动图;详见 1-5-7-4-3-1-3
            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 是动图,那么 image.CGImage 就意味着获取其第一帧
                image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
#endif
            }
        // 如果options是SDImageCacheMatchAnimatedImageClass(匹配图片的类),则走此 else if 分支;枚举值详见 1-5-7-4-3-1-2
        } else if (options & SDImageCacheMatchAnimatedImageClass) {
            // Check image class matching
            Class animatedImageClass = image.class;
             // 开发者设置的图片的类,期望的类
            Class desiredImageClass = context[SDWebImageContextAnimatedImageClass];
            // 如果开发者设置了图片期望的类,但是 image 的类与设置的类不一致则将image置空
            if (desiredImageClass && ![animatedImageClass isSubclassOfClass:desiredImageClass]) {
                image = nil;
            }
        }
    }
    // shouldQueryMemoryOnly:只查询内存缓存;判断只要options不是SDImageCacheQueryMemoryData,立即调用doneBlock将image回传。
    // SDImageCacheQueryMemoryData:去磁盘查找图片二进制数据(image data),枚举值详见1-5-7-4-3-1-2
    BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryMemoryData));
    // 如果只查询内存缓存,则立即调用 doneBlock 将 image 回传
    if (shouldQueryMemoryOnly) {
        if (doneBlock) {
            doneBlock(image, nil, SDImageCacheTypeMemory);
        }
        return nil;
    }

    // 第2步 查找磁盘缓存
    // Second check the disk cache...
    NSOperation *operation = [NSOperation new];
    // Check whether we need to synchronously query disk
    // 1. in-memory cache hit & memoryDataSync
    // 2. in-memory cache miss & diskDataSync
    // 判断我们是否需要同步的去查找磁盘缓存
    // 1. 内存缓存命中并且选择了SDImageCacheQueryMemoryDataSync;枚举值详见1-5-7-4-3-1-2
    // 2. 内存缓存未命中并且选择了SDImageCacheQueryDiskDataSync;枚举值详见1-5-7-4-3-1-2
    BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
                                (!image && options & SDImageCacheQueryDiskDataSync));
    // 定义并实现 queryDiskBlock 来保存一段代码,别急,不出这个方法就会调用这个block
    void(^queryDiskBlock)(void) =  ^{
         // 如果operation被取消,直接调用doneBlock回调各种空值
        if (operation.isCancelled) {
            if (doneBlock) {
                doneBlock(nil, nil, SDImageCacheTypeNone);
            }
            return;
        }
        
        @autoreleasepool {
            // 只是单纯的从磁盘取出图片二进制数据(NSData); 缓存部分会在另外一篇文章详细解析。
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage;
            SDImageCacheType cacheType = SDImageCacheTypeNone;
            if (image) { // 如果内存缓存命中。此 if 分支并不排除 image 和 diskData 都命中的情形。
                // the image is from in-memory cache, but need image data
                diskImage = image;
                cacheType = SDImageCacheTypeMemory;
            } else if (diskData) { // 如果内存缓存未命中并且磁盘缓存命中
                cacheType = SDImageCacheTypeDisk;
                // decode image data only if in-memory cache missed
                // diskImageForKey:data:options:context: 将 data 解码并返回 UIImage;详见1-5-7-4-3-1-4
                diskImage = [self diskImageForKey:key data:diskData options:options context:context];
                // 如果解码成功,并且 config 中 shouldCacheImagesInMemory(默认为YES) 为 YES,则将解码后的图片保存一份到内存缓存;
                if (diskImage && self.config.shouldCacheImagesInMemory) {
                    NSUInteger cost = diskImage.sd_memoryCost;
                    [self.memoryCache setObject:diskImage forKey:key cost:cost];
                }
            }
            // 根据前面的判断来选择同步还是异步调用doneBlock,回传image和data
            if (doneBlock) {
                if (shouldQueryDiskSync) {
                    doneBlock(diskImage, diskData, cacheType);
                } else {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        doneBlock(diskImage, diskData, cacheType);
                    });
                }
            }
        }
    };
    
    // Query in ioQueue to keep IO-safe
    // 根据前面的判断来选择同步还是异步调用刚刚自己实现的 queryDiskBlock
    if (shouldQueryDiskSync) {
        dispatch_sync(self.ioQueue, queryDiskBlock);
    } else {
        dispatch_async(self.ioQueue, queryDiskBlock);
    }
    
    return operation;
}

1-5-7-4-3-1-1

查找内存缓存有两种可能:没有。这不是废话,而是要分开说明有无内存缓存都大概有什么场景。寡人目前能想到的就是下面这些场景,如有其它的场景欢迎留言补充:

无内存缓存:

  1. 第一次使用SD加载这个URL下的图片;这时候去查找是没有内存缓存的。
  2. 第一次启动App;由于刚刚启动,内存中也一定是没有缓存的。
  3. App运行中 && 内存警告 && 未使用弱内存缓存;SD删除了内存缓存,同时也没有弱内存缓存可用。

有内存缓存:

  1. App运行中,第二次使用SD加载这个URL下的图片;
  2. 启动App && 有磁盘缓存,下一次查询缓存就会有内存缓存。因为磁盘缓存被取出后会存到内存缓存一份,以备下次使用;
  3. 如果使用了弱内存缓存,系统发出内存警告时不会删除弱内存缓存而只删除内存缓存,此时弱内存缓存可以当做内存缓存使用;

-[SDImageCache imageFromMemoryCacheForKey:] 查询内存缓存

- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
    return [self.memoryCache objectForKey:key];
}

-[SDMemoryCache objectForKey:] 查询内存缓存

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

1-5-7-4-3-1-2 SDImageCacheOptions枚举值部分解析

  1. SDImageCacheDecodeFirstFrameOnly 是一个 SDImageCacheOptions 类型的枚举值。默认情况下SD会解码动图。这个选项会强制仅解码动图的第一帧并返回这个(第一帧)静态图。
  2. SDImageCacheMatchAnimatedImageClass选项会保证image的类是你提供的类。与SDImageCacheDecodeFirstFrameOnly对立,因为SDImageCacheDecodeFirstFrameOnly总是返回UIImage/NSImage。
  3. SDImageCacheQueryMemoryData 选项会让SD去磁盘异步查找 image data;默认情况下只要是查到内存缓存中有图片(UIImage),就不再去磁盘查找图片的二进制数据了(image data)。
  4. SDImageCacheQueryMemoryDataSync :默认情况下当开发者指定了 SDImageCacheQueryMemoryData 选项后, SD会异步查找 image data。结合这个掩码也可以同步查找 image data。
  5. SDImageCacheQueryDiskDataSync :默认情况下当内存缓存未命中,SD会异步查找磁盘缓存。这个掩码可以强制SD同步查找磁盘缓存(内存缓存未命中时)。
/// Image Cache Options
typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
    /**
     * By default, we do not query image data when the image is already cached in memory. This mask can force to query image data at the same time. However, this query is asynchronously unless you specify `SDImageCacheQueryMemoryDataSync`
     */
    SDImageCacheQueryMemoryData = 1 << 0,
    /**
     * By default, when you only specify `SDImageCacheQueryMemoryData`, we query the memory image data asynchronously. Combined this mask as well to query the memory image data synchronously.
     */
    SDImageCacheQueryMemoryDataSync = 1 << 1,
    /**
     * By default, when the memory cache miss, we query the disk cache asynchronously. This mask can force to query disk cache (when memory cache miss) synchronously.
     @note These 3 query options can be combined together. For the full list about these masks combination, see wiki page.
     */
    SDImageCacheQueryDiskDataSync = 1 << 2,

...

    /*
     * By default, we decode the animated image. This flag can force decode the first frame only and produece the static image.
     */
    SDImageCacheDecodeFirstFrameOnly = 1 << 5,

...

    /**
     * By default, when you use `SDWebImageContextAnimatedImageClass` context option (like using `SDAnimatedImageView` which designed to use `SDAnimatedImage`), we may still use `UIImage` when the memory cache hit, or image decoder is not available, to behave as a fallback solution.
     * Using this option, can ensure we always produce image with your provided class. If failed, a error with code `SDWebImageErrorBadImageData` will been used.
     * Note this options is not compatible with `SDImageCacheDecodeFirstFrameOnly`, which always produce a UIImage/NSImage.
     */
    SDImageCacheMatchAnimatedImageClass = 1 << 7,
...
}

1-5-7-4-3-1-3

SD判断一张图是不是动图有两种方式:

  1. 一是判断 UIImage 的 images 属性是否为nil,非动图这个属性默认为nil;

源码中的 image.sd_isAnimated 就是 SD 专门为 UIImage 自定的一个分类方法:

// UIImage+Metadata.m
- (BOOL)sd_isAnimated {
    return (self.images != nil);
}
  1. 二是根据SD自己的SDAnimatedImage类,这个类继承自UIImage,并且遵循SDAnimatedImage协议。如果这个image对象继承于UIImage类,并且遵循SDAnimatedImage协议,那么这个image就是SDAnimatedImage类,也就是动图。

1-5-7-4-3-1-4

-[SDImageCache diskImageForKey:data:options:context:] 调用解码函数 SDImageCacheDecodeImageData(),输入 NSData,输出 UIImage。

解码部分会在另外一篇文章详细解析。

- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data options:(SDImageCacheOptions)options context:(SDWebImageContext *)context {
    if (data) {
        UIImage *image = SDImageCacheDecodeImageData(data, key, [[self class] imageOptionsFromCacheOptions:options], context);
        return image;
    } else {
        return nil;
    }
}


1-5-7-4-4

-[SDWebImageManager callCompletionBlockForOperation:completion:error:url:] 回调错误信息

- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
                             completion:(nullable SDInternalCompletionBlock)completionBlock
                                  error:(nullable NSError *)error
                                    url:(nullable NSURL *)url {
    [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url];
}

[SDWebImageManager callCompletionBlockForOperation:completion:image:data:error:cacheType:finished:url:] 回调结果

这里的 completionBlock 是 SDInternalCompletionBlock 类型,就是简称 InternalBlock2 的那个。

- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
                             completion:(nullable SDInternalCompletionBlock)completionBlock
                                  image:(nullable UIImage *)image
                                   data:(nullable NSData *)data
                                  error:(nullable NSError *)error
                              cacheType:(SDImageCacheType)cacheType
                               finished:(BOOL)finished
                                    url:(nullable NSURL *)url {
    dispatch_main_async_safe(^{
        if (completionBlock) {
            completionBlock(image, data, error, cacheType, finished, url);
        }
    });
}


1-5-7-4-5 主线方法-下载业务入口

-[SDWebImageManager callDownloadProcessForOperation:url:options:context:cachedImage:cachedData:cacheType:progress:completed:]

在这里实现了requestImageWithURL:方法的 completedBlock, SDWebImageDownloaderCompletedBlock 类型,以下把这个block简称为 LoaderCompletedBlock

// Download process
- (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 {

    // 连续四次判断是否应该下载图片, &= 用法详见1-5-7-4-5-1
    // Check whether we should download image from network
    // 第一次判断 options,如果没有选择 SDWebImageFromCacheOnly 则应该下载; 枚举值详见1-5-7-4-5-2
    BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
    // 第二次判断,若没有缓存图片(cachedImage)或者 options 没有选择 SDWebImageRefreshCached 则应该下载; 注意此处是或的关系,满足其一即为真; 枚举值详见1-5-7-4-5-2
    shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
    // 第三次判断,若代理不能响应 imageManager:shouldDownloadImageForURL: 方法或者 imageManager:shouldDownloadImageForURL: 方法返回 YES 则应该下载
    shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
    // 第四次判断,若 canRequestImageForURL: 方法返回 YES 则应该下载;此处 imageLoader 是 SDWebImageDownloader,详见 1-5-7-4-5-3
    shouldDownload &= [self.imageLoader canRequestImageForURL:url];
    if (shouldDownload) {
        // 能走到这里,说明shouldDownload为真;下面这个 if 分支的条件和第二次判断并不冲突,详见1-5-7-4-5-4
        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 从服务端刷新图片的机会,会尝试重新下载。详见前面1-5-7-2
            [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.
            // 把缓存图片传给 image loader, image loader 要验证服务端图片和缓存图片是否相同。
            SDWebImageMutableContext *mutableContext;
            if (context) {
                mutableContext = [context mutableCopy];
            } else {
                mutableContext = [NSMutableDictionary dictionary];
            }
            mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
            context = [mutableContext copy];
        }
        
        @weakify(operation);
        // 发起网络请求,下载图片;详见1-5-7-4-5-5
        operation.loaderOperation = [self.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
                // 如果operation出错或者被取消,InternalBlock2 回调错误信息
                [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil] url:url];
            } else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
                // Image refresh hit the NSURLCache cache, do not call the completion block
            } else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) {
                // Download operation cancelled by user before sending the request, don't block failed URL
                // InternalBlock2 回调错误信息
                [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
            } else if (error) {
                // InternalBlock2 回调错误信息
                [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
                BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error];
                // 保存下载失败的URL,failedURLs 就是1-5-7-1 SDWebImageRetryFailed 选项提到的黑名单
                if (shouldBlockFailedURL) {
                    SD_LOCK(self.failedURLsLock);
                    [self.failedURLs addObject:url];
                    SD_UNLOCK(self.failedURLsLock);
                }
            } else {
                // 正常情况会走这个分支,保存下载失败的URL,failedURLs 就是1-5-7-1 SDWebImageRetryFailed 选项提到的黑名单
                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];
            }
        }];
    } else if (cachedImage) {
        // 如果查找到缓存图片并且不需要下载,走这个分支。InternalBlock2 回调,回传缓存图片
        [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
        // 代理禁止下载并且没有查找到缓存图片,InternalBlock2 回调,回传各种 nil
        [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
        [self safelyRemoveOperationFromRunning:operation];
    }
}

1-5-7-4-5-1

&= 的用法

这种按位与等于的写法,从最初的赋值开始每一次 &= 操作都将影响下一次结果,如果上一次结果是YES,那么本次 &= 的玩法和 && 没有区别,同真为真。如果上一次结果是NO,那么 &= 就没得玩了,以后无论与谁 &= 都等于NO。也就是说除非结果一直都是YES,否则只要出现一次NO,那么以后都不能玩了。

1-5-7-4-5-2

SDWebImageFromCacheOnly :默认情况下,若缓存未命中,则使用下载的图片。这个选项会阻止默认行为,只使用缓存的图片,没有缓存图片也不下载。

SDWebImageRefreshCached :注释巴拉巴拉说了一大堆,归根结底这个选项就是允许从服务端刷新缓存图片。默认情况下,有了缓存图片就不去服务端再下载一次该URL的图片了,选了这个选项后,即便该URL的图片已经被缓存好,也会去服务端确认一下是否应该刷新。

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
...

/**
     * Even if the image is cached, respect the HTTP response cache control, and refresh the image from remote location if needed.
     * The disk caching will be handled by NSURLCache instead of SDWebImage leading to slight performance degradation.
     * This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics.
     * If a cached image is refreshed, the completion block is called once with the cached image and again with the final image.
     *
     * Use this flag only if you can't make your URLs static with embedded cache busting parameter.
     */
    SDWebImageRefreshCached = 1 << 3,

...

    /**
     * By default, when the cache missed, the image is load from the loader. This flag can prevent this to load from cache only.
     */
    SDWebImageFromCacheOnly = 1 << 15,

...
}

1-5-7-4-5-3

-[SDWebImageDownloader canRequestImageForURL:]
简单粗暴,只要是url存在,就始终返回YES

- (BOOL)canRequestImageForURL:(NSURL *)url {
    if (!url) {
        return NO;
    }
    // Always pass YES to let URLSession or custom download operation to determine
    return YES;
}

1-5-7-4-5-4

第二次判断的条件:!cachedImage || options & SDWebImageRefreshCached
if 分支的条件:shouldDownload 并且 cachedImage && options & SDWebImageRefreshCached

按照源码的写法,如果能走 if 分支,说明第二次判断的条件和 if 分支的条件是可以同时成立的。

首先确定,若 cachedImage && options & SDWebImageRefreshCached 条件成立,则cachedImage 一定不为空,即 !cachedImage 为假;

若 !cachedImage 为假,要想第二次判断条件 !cachedImage || options & SDWebImageRefreshCached 成立(shouldDownload 为真),则 options & SDWebImageRefreshCached 必为真;

因此,第二次判断条件和 if 分支条件同时成立的情况就是:cachedImage 为真;options & SDWebImageRefreshCached 为真;得出 shouldDownload 为真,同时 if 分支条件也成立。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260