【iOS】SDWebImage源码阅读笔记

news2024/11/15 18:52:53

文章目录


前言

最近基本已经将项目完结,这个月开始专心学习源码,将会陆续学习SDWebImage,AFNetworking以及JsonModel的源码


一、设计目的

SDWebImageUIImageViewUIButton提供了下载分类,使我们只需要一行代码即可以实现图片异步下载与缓存功能

二、特性

  1. 异步下载图片
  2. 异步缓存(内存+磁盘),自动管理缓存有效性
  3. 同一个URL不会重复下载
  4. 自动识别无效URL,不会反复重试
  5. 不阻塞主线程
  6. 使用GCD与ARC

三、用法

1.UITableView 中使用 UIImageView+WebCache

UITabelViewCell 中的 UIImageView 控件直接调用 sd_setImageWithURL: placeholderImage:方法即可


2.使用回调Blocks

在 block 中得到图片下载进度和图片加载完成(下载完成或者读取缓存)的回调,如果你在图片加载完成前取消了请求操作,就不会收到成功或失败的回调

    [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
                      placeholderImage:[UIImage imageNamed:@"placeholder.png"]
                             completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
                                    ... completion code here ...
                                 }];

3.SDWebImageManager 的使用

SDWebImageManager是一个单例类,也是SD中的核心类,负责下载与缓存的处理

+ (nonnull instancetype)sharedManager {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}

- (nonnull instancetype)init {
    id<SDImageCache> cache = [[self class] defaultImageCache];
    if (!cache) {
        cache = [SDImageCache sharedImageCache];
    }
    id<SDImageLoader> loader = [[self class] defaultImageLoader];
    if (!loader) {
        loader = [SDWebImageDownloader sharedDownloader];
    }
    return [self initWithCache:cache loader:loader];
}

SDWebImageManager将图片下载和图片缓存组合起来了。SDWebImageManager也可以单独使用。

SDWebImageManager *manager = [SDWebImageManager sharedManager];
    [manager loadImageWithURL:imageURL
                      options:0
                     progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                            // progression tracking code
                     }
                     completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                        if (image) {
                            // do something with image
                        }
                     }];

4.单独使用 SDWebImageDownloader 异步下载图片

我们还可以单独使用SDWebImageDownloader来下载图片,但是图片内容不会缓存到磁盘或是内存

SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
    [downloader downloadImageWithURL:imageURL
                             options:0
                            progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                                // progression tracking code
                            }
                           completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
                                if (image && finished) {
                                    // do something with image
                                }
                            }];

5.单独使用 SDImageCache 异步缓存图片

SDWebImage支持内存缓存与异步的磁盘缓存(可选),如果我们想用SDImageCache来单独缓存数据,也可以和SDWebImageDownloader一样单独使用一个单例

添加缓存的方法:

[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];

默认情况下,图片数据会同时缓存到内存和磁盘中,如果你想只要内存缓存的话,可以使用下面的方法:

[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey toDisk:NO];

读取缓存时可以使用 queryDiskCacheForKey:done: 方法,图片缓存的 key 是唯一的,通常就是图片的 absolute URL。

    SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"];
    [imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image) {
        // image is not nil if image was found
    }];

四、实现原理

我们在这里给出SDWebImage的架构图与流程图

  1. 架构图(UML 类图)
    在这里插入图片描述
  2. 流程图(方法调用顺序图)
    在这里插入图片描述

我们首先通过文字对SDWebImage的方法调用进行分析

  • 首先我们在使用SD时首先调用了[cell.articleImageView sd_setImageWithURL:[NSURL URLWithString:imageURL]];
  • 我们进入这个方法内部,同时来到了UIView+WebCache文件中(这里之所以时UIView是因为UIButton与UIImageView都可以使用SD来进行图片的一系列操作),发现这个方法内部仍然是一个方法
    在这里插入图片描述
    这些方法最后都会调用同一个方法
    - (void)sd_internalSetImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options operationKey:(nullable NSString *)operationKey setImageBlock:(nullable SDSetImageBlock)setImageBlock progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock
  • 我们继续进入sd_internalSetImageWithURL:内部去看他的实现操作,这个方法首先保证了图片加载的唯一性,也就是同一个资源不会进行重复的下载,然后进行一系列加载前的配置操作之后正式开始进行图片的加载操作
  • 图片的加载操作我们就进入到了SDWebImageManager这个类中,在这个类中我们创建一个新的操作去用于管理本次加载,同时通过加锁对错误集合进行访问,查询当前URL是否在我们的错误URL集合中,如果失败则立即完成回调,成功则将当前操作加入到操作队列中,紧接着就会拿着图片缓存的key进行查询缓存以及下载操作
  • 查询缓存操作会进入SDImageCache,默认查询磁盘与内存,当然也可以选择查询。如果是默认查询,首先我们会拿着图片缓存的 key (这个 key 默认是图片 URL)去 SDImageCache 单例中读取内存缓存。
  • 如果没有则会根据需要开启同步或是异步线程查询磁盘缓存,如果查询到了磁盘缓存就会将其同步缓存到内存中去,然后再返回给SDWebImageManager
  • 如果两者都没有,SDWebImageManager 就会调用 SDWebImageDownloader 单例的 -downloadImageWithURL: options: progress: completed: 方法去下载,我们首先会检查是否查询到缓存图像或是需要刷新缓存如果是则会检查是否允许从网络下载图像
  • 如果决定下载图像,会调用图像加载器的requestImageWithURL:方法执行下载任务,传递URL、选项、上下文和进度回调。下载完成后还会通过回调处理结果判断是否将URL添加到错误集合中,如果下载没有问题就会执行callTransformProcessForOperation并将图像保存到缓存中
  • 如果有缓存的图像则直接将缓存图像进行回调,就不会执行下载任务了。
  • 无论下载成功、从缓存获取,还是因为不允许下载,最终都会从当前运行的操作列表中安全地移除当前操作
  • 通过重重回调,要回调的数据沿着SDWebImageDownloaderOperation->SDWebImageDownloader->SDWebImageManager->UIView+WebCache一路流动,其中流动到SDWebImageManager中时对图片进行了缓存,最后在UIView+WebCache中为UIImageView设置了处理好的图片。

我们接下来将会根据方法调用顺序图对我们的源码进行分析

五、具体实现流程

sd_setImageWithURL

  1. UIImageView+WebCache:
    sd_setImageWithURL方法
    在这里插入图片描述
    直接进入内部方法sd_internalSetImageWithURL

sd_internalSetImageWithURL

sd_internalSetImageWithURL方法
该方法是整个SDWebImage实现机制中的核心部分,代码如下:

# pragma mark 调用入口1
- (nullable id<SDWebImageOperation>)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 {
    
    // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode wont
    // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
    //  if url is NSString and shouldUseWeakMemoryCache is true, [cacheKeyForURL:context] will crash. just for a  global protect.
    // 一个常见的错误就是传入的URL不是NSURL类型而是NSString类型,SDWeb允许传入nsstring类型
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }
    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    // 防止因为不是URL类型而导致崩溃
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }
    
    if (context) {
        // 创建副本以避免直接修改可变对象
        context = [context copy];
    } else {
        // 如果没有提供上下文则创建一个空的字典作为上下文
        context = [NSDictionary dictionary];
    }
    // 尝试从上下文中获取键值
    // valid 有效
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
    if (!validOperationKey) {
        // pass through the operation key to downstream, which can used for tracing operation or image view class
        //为了不直接修改传入的上下文对象(这可能会影响其他地方的使用),首先对其进行深复制,得到一个可修改的副本mutableContext。然后在这个副本中设置新的操作键(无论是用户自定义的还是当前类名)。最后,将修改后的可变上下文(mutableContext)再次复制成一个不可变字典,替换原先的context对象,以供后续操作使用。
        validOperationKey = NSStringFromClass([self class]); // 如果不存在,则使用当前类名作为操作键
        SDWebImageMutableContext *mutableContext = [context mutableCopy]; // 复制上下文防止上下文被修改
        mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
        context = [mutableContext copy];
    }
    // 更新最新操作键
    self.sd_latestOperationKey = validOperationKey;
    // 默认情况下,如果没有设置SDWebImageAvoidAutoCancelImage选项,则取消与当前设置图片操作键相关联的所有先前的下载操作。
    if (!(SD_OPTIONS_CONTAINS(options, SDWebImageAvoidAutoCancelImage))) { // SDWebImageAvoidAutoCancelImage —— 不要自动取消之前的下载操作
        // cancel previous loading for the same set-image operation key by default
        [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    }
//    在 UI 开发中,尤其是在列表或滚动视图中,用户可能快速滚动,视图复用机制会导致视图的内容需要频繁更新。如果不取消先前的下载操作,就可能出现以下问题:
//
//    性能问题:同时进行多个不必要的下载任务,增加内存和网络的负担。
//    数据错误:旧的下载任务可能后于新的任务完成,导致视图上显示的图片是错误的。

    
    // 获取或创建与当前操作键关联的图片加载状态对象
    SDWebImageLoadState *loadState = [self sd_imageLoadStateForKey:validOperationKey];
    if (!loadState) {
        loadState = [SDWebImageLoadState new];
    }
    // 设置加载对象的url为当前的url
    loadState.url = url;
    // 将更新后的加载状态对象与当前操作键关联。
    [self sd_setImageLoadState:loadState forKey:validOperationKey];
    
    // 从上下文中获取图片管理器,没有就创建一个
    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];
    }
    
    BOOL shouldUseWeakCache = NO;
    if ([manager.imageCache isKindOfClass:SDImageCache.class]) {
        shouldUseWeakCache = ((SDImageCache *)manager.imageCache).config.shouldUseWeakMemoryCache;
    }
    if (!(options & SDWebImageDelayPlaceholder)) { // 判断是否显示占位图
        if (shouldUseWeakCache) {
            NSString *key = [manager cacheKeyForURL:url context:context];
            // call memory cache to trigger weak cache sync logic, ignore the return value and go on normal query
            // this unfortunately will cause twice memory cache query, but its fast enough
            // in the future the weak cache feature may be re-design or removed
            [((SDImageCache *)manager.imageCache) imageFromMemoryCacheForKey:key];
        }
        dispatch_main_async_safe(^{
            // 立即显示占位图
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        });
    }
#pragma mark 开始图片加载的操作设置
    id <SDWebImageOperation> operation = nil;
    
    if (url) {
        // 重置进度追踪
        // reset the progress
        NSProgress *imageProgress = loadState.progress;
        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
        // 设置block回调, 用于更新UI以及通知调用者
        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);
        // 开始加载图片
        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 callCompletedBlockClosure = ^{ // 设置一个闭包完成回调
                if (!self) { return; }
                if (!shouldNotSetImage) {
                    [self sd_setNeedsLayout]; // 设置图片
                }
                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
            //情况1a:我们得到了一个图像,但SDWebImageAvoidAutoSetImage标志被设置
            //或
            //情况1b:我们没有图像,并且没有设置SDWebImageDelayPlaceholder
            // 根据不同情况处理图片设置
            if (shouldNotSetImage) {
                dispatch_main_async_safe(callCompletedBlockClosure);
                return;
            }
            
            UIImage *targetImage = nil;
            NSData *targetData = nil;
            // 如果加载得到了图片并且现在可以直接加载
            if (image) {
                //情况2a:我们得到一个图像和SDWebImageAvoidAutoSetImage没有设置
                targetImage = image;
                targetData = data;
            } else if (options & SDWebImageDelayPlaceholder) { // 检查options枚举中是否包含SDWebImageDelayPlaceholder选项。
                //情况2b:我们没有图像,并且设置了SDWebImageDelayPlaceholder标志
                targetImage = placeholder;
                targetData = nil;
            }
            
#if SD_UIKIT || SD_MAC
            // 检查是否应该使用图片过渡效果。
            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) {
                    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 options:options basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL callback:callCompletedBlockClosure];
#else
                [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
                callCompletedBlockClosure();
#endif
            });
        }];
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else { // 如果url无效则立即停止小菊花
#if SD_UIKIT || SD_MAC
        [self sd_stopImageIndicator];
#endif
        if (completedBlock) {
            dispatch_main_async_safe(^{ // 设置回调返回错误
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
                completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
            });
        }
    }
    
    return operation;
}

笔者已经为代码添加了注释,我们现在来分析一下具体流程

1.通过validOperationKey取消正在运行的任务

    // 尝试从上下文中获取键值
    // valid 有效
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
    if (!validOperationKey) {
        // pass through the operation key to downstream, which can used for tracing operation or image view class
        //为了不直接修改传入的上下文对象(这可能会影响其他地方的使用),首先对其进行深复制,得到一个可修改的副本mutableContext。然后在这个副本中设置新的操作键(无论是用户自定义的还是当前类名)。最后,将修改后的可变上下文(mutableContext)再次复制成一个不可变字典,替换原先的context对象,以供后续操作使用。
        validOperationKey = NSStringFromClass([self class]); // 如果不存在,则使用当前类名作为操作键
        SDWebImageMutableContext *mutableContext = [context mutableCopy]; // 复制上下文防止上下文被修改
        mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
        context = [mutableContext copy];
    }
    // 更新最新操作键
    self.sd_latestOperationKey = validOperationKey;
    // 默认情况下,如果没有设置SDWebImageAvoidAutoCancelImage选项,则取消与当前设置图片操作键相关联的所有先前的下载操作。
    if (!(SD_OPTIONS_CONTAINS(options, SDWebImageAvoidAutoCancelImage))) { // SDWebImageAvoidAutoCancelImage —— 不要自动取消之前的下载操作
        // cancel previous loading for the same set-image operation key by default
        [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    }
//    在 UI 开发中,尤其是在列表或滚动视图中,用户可能快速滚动,视图复用机制会导致视图的内容需要频繁更新。如果不取消先前的下载操作,就可能出现以下问题:
//
//    性能问题:同时进行多个不必要的下载任务,增加内存和网络的负担。
//    数据错误:旧的下载任务可能后于新的任务完成,导致视图上显示的图片是错误的。

这个方法确保了当前资源仅被下载一次,不会重复下载同一资源,避免内存浪费

2.sd_cancelImageLoadOperationWithKey

#pragma mark 定义一个方法,用于取消与特定键(key)相关联的图片加载操作
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    // 如果调用者没有提供一个有效的键(key),则使用当前实例的类名作为键。
    if (!key) {
        key = NSStringFromClass(self.class);
    }
    // Cancel in progress downloader from queue
    // 从队列中取消下载任务
    // 从当前对象获取存取所有图片的字典
    SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
    // 获取需要取消的图片对象
    id<SDWebImageOperation> operation;
    
    // 进行加锁, 只有一个线程能取消当前加载进程
    // 因为图片加载操作可能在不同的线程中启动或取消,所以需要保护对共享资源的访问。
    @synchronized (self) {
        operation = [operationDictionary objectForKey:key];
    }
    // 实现cancel方法
    if (operation) {
        if ([operation respondsToSelector:@selector(cancel)]) { // 安全调用可选方法  respondsToSelector 判断当前方法是否安全被调用
            [operation cancel];
        }
        // 使用加锁操作对对应的键进行移除操作
        @synchronized (self) {
            [operationDictionary removeObjectForKey:key];
        }
    }
}

获取操作字典与操作队列同时将操作与对应的key从其中移除

3.初始化SDWebImageManager

    // 从上下文中获取图片管理器,没有就创建一个
    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];
    }

4.判断是否需要使用弱缓存,并根据placeholder显示图片

    BOOL shouldUseWeakCache = NO;
    if ([manager.imageCache isKindOfClass:SDImageCache.class]) {
        shouldUseWeakCache = ((SDImageCache *)manager.imageCache).config.shouldUseWeakMemoryCache;
    }
    if (!(options & SDWebImageDelayPlaceholder)) { // 判断是否显示占位图
        if (shouldUseWeakCache) {
            NSString *key = [manager cacheKeyForURL:url context:context];
            // call memory cache to trigger weak cache sync logic, ignore the return value and go on normal query
            // this unfortunately will cause twice memory cache query, but it's fast enough
            // in the future the weak cache feature may be re-design or removed
            [((SDImageCache *)manager.imageCache) imageFromMemoryCacheForKey:key];
        }
        dispatch_main_async_safe(^{
            // 立即显示占位图
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        });
    }

5.判断是否有url同时进入下一阶段

if (url) {
	//code in here
}

接下来分析的代码都在上方判断url是否存在的if分支内部

6.进行加载图片前的一系列配置操作比如小菊花等

#pragma mark 开始图片加载的操作设置
    id <SDWebImageOperation> operation = nil;
    
    if (url) {
        // 重置进度追踪
        // reset the progress
        NSProgress *imageProgress = loadState.progress;
        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
        // 设置block回调, 用于更新UI以及通知调用者
        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);
            }
        };

7.加载内容

其中进入block前调用@weakify(self);进入block后调用@strongify(self);,作用是避免循环引用

当一个 block 捕获了 self 时,它会对 self 产生一个强引用。
如果 self 同时也强引用了这个 block(例如,将这个 block 作为一个属性或者实例变量存储),那么就会产生一个循环引用。
循环引用会导致self与Block都无法正确被释放

        // 用弱饮用避免循环引用
        @weakify(self);
        // 开始加载图片
        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;
            }

紧接着我们进入loadImageWithURL

loadImageWithURL

一样的我们给出整个部分的实现流程,然后再逐步分析

#pragma mark 定义一个方法来加载图片,接收图片的URL、加载选项、上下文信息、进度回调和完成回调作为参数
- (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
    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的类型
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }
    // 创建一个新的操作用于管理这次加载
    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    operation.manager = self;

    BOOL isFailedUrl = NO;
    if (url) { // 如果url存在,检查它是否在访问失败的URL列表里
        SD_LOCK(_failedURLsLock); // 加锁是为了防止多个线程访问同一个资源,比如这里的self.failedURLs就属于共享资源,为了防止其他线程在当前线程访问时对其进行修改操作所以要加锁
        isFailedUrl = [self.failedURLs containsObject:url];
        SD_UNLOCK(_failedURLsLock);
    }
    
    // 预处理选项和上下文参数,确定最终的结果。
    SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];

    //如果URL无效或是失败的URL没有设置重试选项, 立即调用完成回调
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil";
        NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL;
        // 调用完成回调,返回错误信息
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] queue:result.context[SDWebImageContextCallbackQueue] url:url];
        return operation; // 返回操作实例
    }

    // 将当前操作添加到正在运行的操作列表中并且进行加锁保证线程安全
    SD_LOCK(_runningOperationsLock);
    [self.runningOperations addObject:operation];
    SD_UNLOCK(_runningOperationsLock);
    
    // Start the entry to load image from cache, the longest steps are below 启动从缓存中加载图片最长的流程如下
    // Steps without transformer: // 没有变换器的流程, 变换器指的是对图像进行加工的工具
    // 1. query image from cache, miss // 从缓存中查询图像, 如果缓存中没有图像
    // 2. download data and image // 下载数据以及图像
    // 3. store image to cache // 并将其存储到缓存中
    
    // Steps with transformer: //
    // 1. query transformed image from cache, miss // 从缓存中查询已变换的图像,如果没有
    // 2. query original image from cache, miss // 在缓存中查询原始图像, 如果没有
    // 3. download data and image // 下载数据与图像
    // 4. do transform in CPU // 在CPU中完成转换操作
    // 5. store original image to cache // 将原始图像存储到缓存中
    // 6. store transformed image to cache // 将变换后的图像存储到缓存中
    // 这里体现出模块化设计,面试可以讲
    [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];

    return operation;
}

1.创建一个新的操作用于管理这次加载

    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    operation.manager = self;

2.检查目前的URL是否在先前的失败访问集合中

    BOOL isFailedUrl = NO;
    if (url) { // 如果url存在,检查它是否在访问失败的URL列表里
        SD_LOCK(_failedURLsLock); // 加锁是为了防止多个线程访问同一个资源,比如这里的self.failedURLs就属于共享资源,为了防止其他线程在当前线程访问时对其进行修改操作所以要加锁
        isFailedUrl = [self.failedURLs containsObject:url];
        SD_UNLOCK(_failedURLsLock);
    }

这里通过递归锁保证了对共享资源的安全访问,共享资源就是self.failedURLs之所以加锁是为了防止其他线程访问当前集合并对其进行修改

3.如果URL无效或是失败的URL没有设置重试选项, 立即调用完成回调并返回错误信息

    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil";
        NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL;
        // 调用完成回调,返回错误信息
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] queue:result.context[SDWebImageContextCallbackQueue] url:url];
        return operation; // 返回操作实例
    }

4.如果上述都没问题就将当前操作加到操作队列中并且预处理最终结果

    // 预处理选项和上下文参数,确定最终的结果。
    SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
    // 将当前操作添加到正在运行的操作列表中并且进行加锁保证线程安全
    SD_LOCK(_runningOperationsLock);
    [self.runningOperations addObject:operation];
    SD_UNLOCK(_runningOperationsLock);

紧接着我们进入callCacheProcessForOperation中查找缓存

callCacheProcessForOperation

// 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 = context[SDWebImageContextImageCache];
    if (!imageCache) {
        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与上下文生成缓存键
        NSString *key = [self cacheKeyForURL:url context:context];
        // to avoid the SDImageCache's sync logic use the mismatched cache key
        // we should strip the `thumbnail` related context
        // 为了避免SDImageCache的同步逻辑使用不匹配的缓存键,我们需要移除与缩略图相关的上下文
        SDWebImageMutableContext *mutableContext = [context mutableCopy];
        mutableContext[SDWebImageContextImageThumbnailPixelSize] = nil;
        mutableContext[SDWebImageContextImagePreserveAspectRatio] = nil;
        @weakify(operation);
        // 查询缓存的操作
        operation.cacheOperation = [imageCache queryImageForKey:key options:options context:mutableContext cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
            @strongify(operation);
            if (!operation || operation.isCancelled) {
                // 如果操作被取消或是不存在则返回错误
                [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] queue:context[SDWebImageContextCallbackQueue] url:url];
                // 安全从运行操作列表中移除操作
                [self safelyRemoveOperationFromRunning:operation];
                return;
            } else if (!cachedImage) { // 如果缓存中图片不存在,再去查询原始缓存  // 缓存与原始缓存的区别是一个图像是否经过了变换
                NSString *originKey = [self originalCacheKeyForURL:url context:context];
                BOOL mayInOriginalCache = ![key isEqualToString:originKey];
                // Have a chance to query original cache instead of downloading, then applying transform
                // Thumbnail decoding is done inside SDImageCache's decoding part, which does not need post processing for transform
                if (mayInOriginalCache) { // 可能存在在原始缓存中,就用原始缓存查询流程
                    [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];
    }
}

1.首先在函数中获取查询图像或是查询类型

 // Grab the image cache to use
    // 获取需要查询的缓存图像,如果上下文中有则优先从上下文中获取,否则就从当前类中获取
    id<SDImageCache> imageCache = context[SDWebImageContextImageCache];
    if (!imageCache) {
        imageCache = self.imageCache;
    }
    // Get the query cache type
    // 获取缓存查询类型,默认查询所有类型的缓存(内存和磁盘)
    SDImageCacheType queryCacheType = SDImageCacheTypeAll;
    if (context[SDWebImageContextQueryCacheType]) {
        queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue];
    }

2.同样移除先前不匹配的上下文

    if (shouldQueryCache) {
        // 根据url与上下文生成缓存键
        NSString *key = [self cacheKeyForURL:url context:context];
        // to avoid the SDImageCache's sync logic use the mismatched cache key
        // we should strip the `thumbnail` related context
        // 为了避免SDImageCache的同步逻辑使用不匹配的缓存键,我们需要移除与缩略图相关的上下文
        SDWebImageMutableContext *mutableContext = [context mutableCopy];
        mutableContext[SDWebImageContextImageThumbnailPixelSize] = nil;
        mutableContext[SDWebImageContextImagePreserveAspectRatio] = nil;

3.进入SDWebCache进行缓存查询操作

 mutableContext[SDWebImageContextImagePreserveAspectRatio] = nil;
        @weakify(operation);
        // 查询缓存的操作
        operation.cacheOperation = [imageCache queryImageForKey:key options:options context:mutableContext cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
            @strongify(operation);

queryImageForKey

- (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
    if (!key) { // 如果缓存键为空,则立即完成回调
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    // Invalid cache type
    // 如果缓存类型为无也立即完成回调
    if (queryCacheType == SDImageCacheTypeNone) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    
    // First check the in-memory cache...
    // 首先检查内存缓存
    UIImage *image;
    // 如果查询类型没有要查询磁盘, 则直接只查询内存
    if (queryCacheType != SDImageCacheTypeDisk) {
        image = [self imageFromMemoryCacheForKey:key];
    }
    // 如果找到了图像
    if (image) {
        // 只解码第一帧保证图片是静态的
        if (options & SDImageCacheDecodeFirstFrameOnly) {
            // Ensure static image
            if (image.sd_imageFrameCount > 1) {
#if SD_MAC
                image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
#else
                image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
#endif
            }
        } else if (options & SDImageCacheMatchAnimatedImageClass) {
            // Check image class matching
            Class animatedImageClass = image.class;
            Class desiredImageClass = context[SDWebImageContextAnimatedImageClass];
            if (desiredImageClass && ![animatedImageClass isSubclassOfClass:desiredImageClass]) {
                image = nil;
            }
        }
    }

    // 检查是否只需要查询内存,只查询内存的话之后立即回调,不再查询磁盘
    BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));
    if (shouldQueryMemoryOnly) {
        if (doneBlock) {
            doneBlock(image, nil, SDImageCacheTypeMemory);
        }
        return nil;
    }
    
    // 接下来查询磁盘缓存
    SDCallbackQueue *queue = context[SDWebImageContextCallbackQueue];
    SDImageCacheToken *operation = [[SDImageCacheToken alloc] initWithDoneBlock:doneBlock];
    operation.key = key; // 用于查询对象
    operation.callbackQueue = queue; // 设置操作队列
    // Check whether we need to synchronously query disk
    // 1. in-memory cache hit & memoryDataSync
    // 2. in-memory cache miss & diskDataSync
    // 根据是否需要同步处理,选择同步或异步查询磁盘
    BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
                                (!image && options & SDImageCacheQueryDiskDataSync));
    // 定义从磁盘查询数据的Block
    NSData* (^queryDiskDataBlock)(void) = ^NSData* { // 定义Block,对取消操作进行加锁
        @synchronized (operation) {
            if (operation.isCancelled) {
                return nil;
            }
        }
        // 如果操作没有被取消,从所有可能路径中搜索数据
        return [self diskImageDataBySearchingAllPathsForKey:key];
    };
    // 定义从磁盘创建图像的Block
    UIImage* (^queryDiskImageBlock)(NSData*) = ^UIImage*(NSData* diskData) {
        @synchronized (operation) {
            if (operation.isCancelled) {
                return nil;
            }
        }
        
        UIImage *diskImage;
        if (image) {
            // the image is from in-memory cache, but need image data
            // 如果已经在内存中找到图像,但是需要图像数据
            diskImage = image;
        } else if (diskData) {
            BOOL shouldCacheToMomery = YES;
            if (context[SDWebImageContextStoreCacheType]) { // 检查是否应该将图像缓存到内存中
                SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
                shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
            }
            // 特殊情况:如果用户查询同一URL的图像以避免多次解码和写入相同的图像对象到磁盘缓存中,我们在这里再次查询和检查内存缓存
            if (shouldCacheToMomery && self.config.shouldCacheImagesInMemory) {
                diskImage = [self.memoryCache objectForKey:key];
            }
            // 如果内存缓存未命中,解码磁盘数据
            if (!diskImage) {
                diskImage = [self diskImageForKey:key data:diskData options:options context:context];
                // check if we need sync logic
                if (shouldCacheToMomery) {
                    [self _syncDiskToMemoryWithImage:diskImage forKey:key]; // 将数据同步缓存到内存中
                }
            }
        }
        return diskImage;
    };
    
    // Query in ioQueue to keep IO-safe
    // 用IO队列保证IO操作安全
    // 同步执行磁盘查询
    if (shouldQueryDiskSync) {
        __block NSData* diskData;
        __block UIImage* diskImage;
        dispatch_sync(self.ioQueue, ^{
            diskData = queryDiskDataBlock();
            diskImage = queryDiskImageBlock(diskData);
        });
        if (doneBlock) {
            doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
        }
    } else {
        // 异步执行查询操作
        dispatch_async(self.ioQueue, ^{
            NSData* diskData = queryDiskDataBlock();
            UIImage* diskImage = queryDiskImageBlock(diskData);
            @synchronized (operation) {
                if (operation.isCancelled) {
                    return;
                }
            }
            if (doneBlock) {
                [(queue ?: SDCallbackQueue.mainQueue) async:^{
                    // Dispatch from IO queue to main queue need time, user may call cancel during the dispatch timing
                    // This check is here to avoid double callback (one is from `SDImageCacheToken` in sync)
                    @synchronized (operation) {
                        if (operation.isCancelled) {
                            return;
                        }
                    }
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                }];
            }
        });
    }
    
    return operation;
}

1.首先检查缓存键,为无或是为空都直接返回nil

 if (!key) { // 如果缓存键为空,则立即完成回调
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    // Invalid cache type
    // 如果缓存类型为无也立即完成回调
    if (queryCacheType == SDImageCacheTypeNone) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

2.接着首先检查内存缓存,如果找到了图像则保存第一帧

// 首先检查内存缓存
    UIImage *image;
    // 如果查询类型没有要查询磁盘, 则直接只查询内存
    if (queryCacheType != SDImageCacheTypeDisk) {
        image = [self imageFromMemoryCacheForKey:key];
    }
    // 如果找到了图像
    if (image) {
        // 只解码第一帧保证图片是静态的
        if (options & SDImageCacheDecodeFirstFrameOnly) {
            // Ensure static image
            if (image.sd_imageFrameCount > 1) {
#if SD_MAC
                image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
#else
                image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
#endif
            }
        } else if (options & SDImageCacheMatchAnimatedImageClass) {
            // Check image class matching
            Class animatedImageClass = image.class;
            Class desiredImageClass = context[SDWebImageContextAnimatedImageClass];
            if (desiredImageClass && ![animatedImageClass isSubclassOfClass:desiredImageClass]) {
                image = nil;
            }
        }
    }

3.检查是否只需要查询内存,只查询内存的话之后立即回调,不再查询磁盘

    BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));
    if (shouldQueryMemoryOnly) {
        if (doneBlock) {
            doneBlock(image, nil, SDImageCacheTypeMemory);
        }
        return nil;
    }

4.接下来选择异步还是同步查询磁盘缓存

 BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
                                (!image && options & SDImageCacheQueryDiskDataSync));
    // 定义从磁盘查询数据的Block
    NSData* (^queryDiskDataBlock)(void) = ^NSData* { // 定义Block,对取消操作进行加锁
        @synchronized (operation) {
            if (operation.isCancelled) {
                return nil;
            }
        }
        // 如果操作没有被取消,从所有可能路径中搜索数据
        return [self diskImageDataBySearchingAllPathsForKey:key];
    };
    // 定义从磁盘创建图像的Block
    UIImage* (^queryDiskImageBlock)(NSData*) = ^UIImage*(NSData* diskData) {
        @synchronized (operation) {
            if (operation.isCancelled) {
                return nil;
            }
        }

5.判断是否将查询到的磁盘数据缓存到内存缓存中

            // 如果内存缓存未命中,解码磁盘数据
            if (!diskImage) {
                diskImage = [self diskImageForKey:key data:diskData options:options context:context];
                // check if we need sync logic
                if (shouldCacheToMomery) {
                    [self _syncDiskToMemoryWithImage:diskImage forKey:key]; // 将数据同步缓存到内存中
                }
            }

6.如果缓存中图片不存在,再去查询原始缓存

缓存与原始缓存的区别是一个图像是否经过了变换

else if (!cachedImage) { // 如果缓存中图片不存在,再去查询原始缓存  // 缓存与原始缓存的区别是一个图像是否经过了变换
                NSString *originKey = [self originalCacheKeyForURL:url context:context];
                BOOL mayInOriginalCache = ![key isEqualToString:originKey];
                // Have a chance to query original cache instead of downloading, then applying transform
                // Thumbnail decoding is done inside SDImageCache's decoding part, which does not need post processing for transform
                if (mayInOriginalCache) { // 可能存在在原始缓存中,就用原始缓存查询流程
                    [self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
                    return;
                }

如果内存缓存与磁盘缓存中都没有找到那么就启用下载流程,如果找到了就直接回调,后面callDownloadProcessForOperation方法会判断是否需要下载

callDownloadProcessForOperation

// 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 {
    // Mark the cache operation end
    // 标记缓存操作结束
    @synchronized (operation) {
        operation.cacheOperation = nil;
    }
    
    // Grab the image loader to use
    // 获取默认加载器
    id<SDImageLoader> imageLoader = context[SDWebImageContextImageLoader];
    if (!imageLoader) {
        imageLoader = self.imageLoader;
    }
    
    // Check whether we should download image from network
    // 检查是否需要从网上下载图片
    BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
    shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached); // 如果需要刷新缓存或者缓存中没有图像
    shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]); // 委托是否允许下载
    if ([imageLoader respondsToSelector:@selector(canRequestImageForURL:options:context:)]) {
        shouldDownload &= [imageLoader canRequestImageForURL:url options:options context:context];
    } else {
        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.
            [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES queue:context[SDWebImageContextCallbackQueue] 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"}] queue:context[SDWebImageContextCallbackQueue] 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
                [self callCompletionBlockForOperation:operation completion:completedBlock error:error queue:context[SDWebImageContextCallbackQueue] url:url];
            } else if (error) {
                [self callCompletionBlockForOperation:operation completion:completedBlock error:error queue:context[SDWebImageContextCallbackQueue] 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 transform process
                // 继续图像转换流程, 同时保存图像到缓存中
                [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:SDImageCacheTypeNone finished:finished 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 queue:context[SDWebImageContextCallbackQueue] 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 queue:context[SDWebImageContextCallbackQueue] url:url];
        [self safelyRemoveOperationFromRunning:operation];
    }
}

1.首先标记缓存操作结束

    // Mark the cache operation end
    // 标记缓存操作结束
    @synchronized (operation) {
        operation.cacheOperation = nil;
    }

2.如果需要下载就发起下载请求

 operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {

3.下载失败便将url加到错误集合中

            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"}] queue:context[SDWebImageContextCallbackQueue] 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
                [self callCompletionBlockForOperation:operation completion:completedBlock error:error queue:context[SDWebImageContextCallbackQueue] url:url];
            } else if (error) {
                [self callCompletionBlockForOperation:operation completion:completedBlock error:error queue:context[SDWebImageContextCallbackQueue] 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);
                }

4.进行图像转换流程并进行图像存储操作

[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:SDImageCacheTypeNone finished:finished completed:completedBlock];

这里不对图像转换进行相关描述,着重于保存操作

storeImage

1.将图像存储到内存缓存中

if (image && toMemory && self.config.shouldCacheImagesInMemory) {
    NSUInteger cost = image.sd_memoryCost;
    [self.memoryCache setObject:image forKey:key cost:cost];
}

2.决定是否继续存储到磁盘

如果不需要存储到磁盘,则调用完成回调并返回。

if (!toDisk) {
    if (completionBlock) {
        completionBlock();
    }
    return;
}

3.将数据存储到磁盘中

            dispatch_async(self.ioQueue, ^{
                [self _storeImageDataToDisk:encodedData forKey:key];
                [self _archivedDataWithImage:image forKey:key];
                if (completionBlock) {
                    [(queue ?: SDCallbackQueue.mainQueue) async:^{
                        completionBlock();
                    }];
                }
            });
        });
    } else {
        dispatch_async(self.ioQueue, ^{
            [self _storeImageDataToDisk:data forKey:key];
            [self _archivedDataWithImage:image forKey:key];
            if (completionBlock) {
                [(queue ?: SDCallbackQueue.mainQueue) async:^{
                    completionBlock();
                }];
            }
        });

setImage

下载成功后,通过重重回调,要回调的数据沿着SDWebImageDownloaderOperation->SDWebImageDownloader->SDWebImageManager->UIView+WebCache一路流动,其中流动到SDWebImageManager中时对图片进行了缓存,最后在UIView+WebCache中为UIImageView设置了处理好的图片。

这里我们重新回到sd_internalSetImageWithURL方法中,更新一系列外部配置像图片过度效果等后,在主线程调用sd_setImage更新UI

if (setImageBlock) {
        finalSetImageBlock = setImageBlock;
    } else if ([view isKindOfClass:[UIImageView class]]) {
        UIImageView *imageView = (UIImageView *)view;
        finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData, SDImageCacheType setCacheType, NSURL *setImageURL) {
            imageView.image = setImage;
        };
    }
#if SD_UIKIT
    else if ([view isKindOfClass:[UIButton class]]) {
        UIButton *button = (UIButton *)view;
        finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData, SDImageCacheType setCacheType, NSURL *setImageURL) {
            [button setImage:setImage forState:UIControlStateNormal];
        };
    }

通过判断类是button还是imageview来设置不同设置方法

总结

由此我们总结一下SDWebImage的调用流程

首先我们会进入setImagewithURL:方法中,然后进入sd-InternalmageWithURL方法中,在这个方法中我们首先通过validOperationKey取消正在运行的任务,任务是通过sd_cancelImageLoadOperationWithKey方法取消的,这一步是为了避免同一资源被重复下载,接着我们初始化SDWebManager(这里因为SDWebManager是单例,所以只初始化一次),接着进行一系列配置后调用loadImageWithURL方法,首先检查URL是否在错误的集合中,如果没有就调用queryImageForKey去查找缓存,查找缓存的步骤是首先查找内存缓存,内存缓存找不到再去查找磁盘缓存,都找不到则去查询原始数据。如果都找不到我们就去执行下载操作,下载操作完成后通过storeImage方法将图像存储到缓存中,最后回到SDWebImageManager单例类中通过setImage方法将Image设置在对应的视图上

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1595941.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

实习僧网站的实习岗位信息分析

目录 背景描述数据说明数据集来源问题描述分析目标以及导入模块1. 数据导入2. 数据基本信息和基本处理3. 数据处理3.1 新建data_clean数据框3.2 数值型数据处理3.2.1 “auth_capital”&#xff08;注册资本&#xff09;3.2.2 “day_per_week”&#xff08;每周工作天数&#xf…

Matlab-AMF算法(自适应中值滤波Adaptive Median Filtering)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 实现原理 AMF&#xff08;Adaptive Median Filter&#xff0c;自适应中值滤波&#xff09;是一种用于图像处理和信号处理的滤波算…

OpenHarmony南向开发案例:【智能垃圾桶】

样例简介 智能垃圾桶可以通过数字管家应用来监测垃圾桶当前可用容量&#xff0c;提醒主人及时处理垃圾&#xff1b;通过日程管家可以实现和其他智能设备联动。 核心组件位置功能距离传感器置于垃圾桶盖内侧感应垃圾量红外传感器置于垃圾桶前端感应是否有人靠近光敏电阻开发板…

SpringMvc文件上传下载案例

1、文件上传 文件上传功能如下&#xff1a; 客户端使用ElementPlus上传文件 服务器使用 SpringMvc接收文件 1.1客户端代码 ElementPlus的<el-upload>可以非常方便的处理文件上传功能&#xff0c;即美观又强大。 传送门--》 upload组件文档 1.1.1、使用axios工具上传…

Java算法之时间复杂度和空间复杂度的概念和计算

1. 算法效率 如何去衡量一个算法的好坏&#xff1f; 通常我们从时间效率和空间效率两个方面去分析算法的好坏。时间效率即时间复杂度&#xff0c;空间效率被称为空间复杂度。时间复杂度主要是衡量一个算法的运行速度&#xff0c;而空间复杂度主要衡量一个算法所需要的额外空间…

【面试题】s += 1 和 s = s + 1的区别

文章目录 1.问题2.发现过程3.解析 1.问题 以下两个程序真的完全等同吗&#xff1f; short s 0; s 1; short s 0; s s 1; 2.发现过程 初看s 1 和 s s 1好像是等价的&#xff0c;没有什么区别。很长一段时间内我也是这么觉得&#xff0c;因为当时学习c语言的时候教科书…

系统思考—时间滞延

“没有足够的时间是所有管理问题的一部分。”——彼得德鲁克 鱼和熊掌可以兼得&#xff0c;但并不能同时获得。在提出系统解决方案时&#xff0c;我们必须认识到并考虑到解决方案的实施通常会有必要的时间滞延。这种延迟有时比我们预想的要长得多&#xff0c;特别是当方案涉及…

AlgorithmDay11

day11 对于-*/这些运算符&#xff0c;它们并不是单个字符&#xff0c;而是由两个字符组成的运算符。在C中&#xff0c;这些运算符是作为字符串处理的&#xff0c;因此应该使用双引号来表示。 例如&#xff1a; string op ""; // 表示加法运算符另一方面&#xff…

vue3+高德地图(或echarts)+turfjs实现等压线,色斑图(用于显示气象,环境等地图场景)

首先是turf.js(英文官网),也有中文网不过也就目录翻译了一下. 高德官网自行获得key echarts官网 使用turf的isobands api实现. 数据: 需要准备geojson格式经纬度信息业务值(比如温度,高度,光照只要是number值什么数据都可以) 国内各地区geojson数据点这里获得 参考的是这位大佬…

30元腾讯云服务器搭建幻兽帕鲁Palworld多人联机游戏,畅玩

幻兽帕鲁太火了&#xff0c;官方palworld服务器不稳定&#xff1f;不如自建服务器&#xff0c;基于腾讯云幻兽帕鲁服务器成本32元全自动部署幻兽帕鲁服务器&#xff0c;超简单有手就行&#xff0c;全程自动化一键部署10秒钟即可搞定&#xff0c;无需玩家手动部署幻兽帕鲁游戏程…

[通俗易懂:Linux标准输入/输出和重定向]Shell脚本之 > /dev/null 2>1命令详解

目录标题 一、> /dev/null 2>&1 命令解析二、/dev/null 文件浅显理解三、标准输入、标准输出、标准错误输出四、输入重定向、输出重定向五、命令作用与应用场景 如果想看命令意义&#xff0c;可以直接跳到第五部分 一、> /dev/null 2>&1 命令解析 我们在别…

7 个 Python 问题,来扫扫盲

这 7 个问题&#xff0c;我是有收获的&#xff0c;整理如下&#xff1a; 1、反射算术运算符 你可能知道 Python 里面的魔法函数&#xff0c;比如 __add__ 和 __sub__ 代表 - 运算符&#xff0c;表示 obj /- something&#xff0c;但你可能不知道还有一个 __radd__&#xff0…

JMeter入门教程 —— 事务!

简介&#xff1a; JMeter中事务的基本介绍 1.任务背景 JMeter中的事务是通过事务控制器实现的。&#xff0c;为了衡量服务器对某一个或一系列操作处理的响应时间&#xff0c;需要定义事务。下面我们详细介绍在JMeter中如何使用事务 2.任务目标 掌握基于JMeter性能测试脚本开…

标准版uni-app移动端页面添加/开发操作流程

页面简介 uni-app项目中&#xff0c;一个页面就是一个符合Vue SFC规范的.vue文件或.nvue文件。 .vue页面和.nvue页面&#xff0c;均全平台支持&#xff0c;差异在于当uni-app发行到App平台时&#xff0c;.vue文件会使用webview进行渲染&#xff0c;.nvue会使用原生进行渲染。…

若依框架针对漏洞升级的记录

背景&#xff1a;项目部署在生产环境上以后&#xff0c;漏洞扫描的时候&#xff0c;发现各种漏洞需要修复&#xff0c;很多漏洞的升级后面都记不住了&#xff0c;所以现在都简单记录一下处理的步骤 20240415 解决方案&#xff1a;直接修改配置文件中的Spring security的版本…

bugku-web-需要管理员

页面源码 <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetUTF-8"> <title>404 Not Found</title> </head> <body> <div idmain><i> <h2>Something error:</h2…

Linux:调试器 - gdb

Linux&#xff1a;调试器 - gdb gbd基本概念gbd调试浏览断点运行变量 gbd基本概念 GDB (GNU Debugger) 是一个强大的命令行调试工具,用于调试各种编程语言(如C、C、Java、Python等)编写的程序。使用 gdb可以帮助开发人员更快地定位和修复程序中的缺陷,提高代码质量和开发效率。…

大众日报教育版的 投稿方式-最快一周内见报

大众日报教育版的 投稿方式-最快一周内见报 大众日报是中共山东省委机关报&#xff0c;创刊于1939年1月1日&#xff0c;由中国共产党领导下的八路军budui所创建&#xff0c;是中共在山东省发行的报纸。 大众日报的“大众教育”版块是该报的一个重要组成部分&#xff0c;主要关…

React-项目构建

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;React篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来React篇专栏内容:React-项目构建 目录 1、初始化项目 2、目录结构 组件 1、组件的创建方式 1.1、函数创建组…

【报错解决】RuntimeError: Distributed package doesn‘t have NCCL built in

报错信息&#xff1a; raise RuntimeError("Distributed package doesnt have NCCL " "built in") RuntimeError: Distributed package doesnt have NCCL built in报错原因&#xff1a; windows系统不支持nccl&#xff0c;采用gloo&#xff1b; 报错解决&…