iOS--SDWebImage源码

news2024/11/22 23:59:58

文章目录

  • 前言
    • 它提供了UIImageView的一个分类,支持从网路上下载且缓存图片,并设置图片到对应的UIImageView控件或者UIButton控件。
  • SDWebImage简介
  • 官方图解
    • 主序列图(Main Sequence Disagram)
    • 顶层API图(Top Level API Diagram)
    • 整体类图(Overall Class Diagram)
  • SDWebImage功能
    • 核心类
    • 概念框架
  • 源码
    • UIView+WebCache
    • 取消之前下载操作
    • 设置占位图
    • 判断URL是否合法
  • 工具层
    • SDWebImageManager
    • loadImageWithURL
    • 先来看第一个,返回SDWebImageOptionsResult:
    • 第二个callCacheProcessForOperation的调用
    • 在这里调用了下载方法callDownloadProcessForOperation
    • SDImageCache
    • SDWebImageDownloader


前言

SDWebImage这个库提供了具有缓存支持的异步图像下载器。

它提供了UIImageView的一个分类,支持从网路上下载且缓存图片,并设置图片到对应的UIImageView控件或者UIButton控件。

提示:以下是本篇文章正文内容,下面案例可供参考

SDWebImage简介

  • 提供了一个UIImageView的category用来加载网络图片并且对网络图片的缓存进行管理
  • 采用异步方法来下载网络图片
  • 采用异步的方法,使用内存+磁盘来缓存网络图片,拥有自动的缓存国旗处理机制
  • 支持的图片格式包括PNG,JPEG,GIF,Webp,等
  • 支持GIF动图(4.0之前的效果不太好,4.0后基于FLAnimatedImage加载动图)
  • 支持后台的图片解压缩处理
  • 支持Arm64
  • 同一个url的图片不会被重复下载
  • 失效,虚假的url不会被无限重试
  • 耗时操作都在子线程,确保不阻塞主线程
  • 使用GCD和ARC

官方图解

主序列图(Main Sequence Disagram)

在这里插入图片描述

webcache
Web缓存是指在Web浏览器或Web代理服务器中保存已经获取的Web资源(如HTML页面、CSS样式表、JavaScript代码、图像、视频、音频等)的一种技术。Web缓存通常是在本地磁盘或内存中进行的,目的是提高Web应用程序的性能、降低网络带宽的使用和减少Web服务器的负载。

Web缓存的工作原理如下:

当用户请求访问一个Web资源时,浏览器或代理服务器会首先检查本地缓存是否已经存在该资源。
如果本地缓存已经存在该资源,并且缓存资源仍然有效(即未过期),则浏览器或代理服务器会直接从本地缓存中获取资源,并将其返回给用户。
如果本地缓存不存在该资源,或者缓存资源已经过期,浏览器或代理服务器将向Web服务器发送请求,获取最新的资源并存储到本地缓存中。
Web缓存的好处包括:

减少网络带宽的使用:当Web资源被缓存在本地,用户在访问相同资源时无需从Web服务器下载,从而节省了网络带宽的使用。
提高Web应用程序的性能:当Web资源被缓存在本地,用户在访问相同资源时可以更快地加载,从而提高了Web应用程序的性能。
减轻Web服务器的负载:当Web资源被缓存在本地,用户在访问相同资源时无需从Web服务器下载,从而减轻了Web服务器的负载。
总的来说,Web缓存是一种非常重要的技术,它可以提高Web应用程序的性能、降低网络带宽的使用和减轻Web服务器的负载。

SDWebImageManager
SDWebImageManager是SDWebImage的核心管理器,它负责协调下载器和缓存器,并提供了一个简单的接口用于加载和取消图像的下载和缓存。它是一个单例对象,可以在整个应用程序中使用。

SDWebImageManager的主要功能包括:

下载图像:SDWebImageManager通过实例化下载器来下载图像,并将下载后的图像传递给缓存器进行缓存。它支持异步下载图像并提供回调机制。
缓存图像:SDWebImageManager可以将下载的图像缓存到磁盘和内存中,以便在以后的访问中更快地加载图像。
取消下载和缓存:SDWebImageManager允许取消正在下载和缓存的操作。
自定义缓存和下载策略:SDWebImageManager允许开发人员根据自己的需求自定义缓存和下载策略。
图像解码器:SDWebImageManager支持多种图像格式的解码器,并允许开发人员自定义图像解码器。
扩展:SDWebImageManager支持扩展,例如WebP格式和GIF动画格式。
SDWebImageManager是一个非常强大的图像加载和缓存框架,它可以帮助开发人员轻松地实现异步图像加载和缓存,并提高应用程序的性能。

顶层API图(Top Level API Diagram)

在这里插入图片描述

整体类图(Overall Class Diagram)

在这里插入图片描述

SDWebImage功能

请添加图片描述

核心类

  • SDWebImageDownloader:负责维持图片的下载队列,是一个单例对象
  • SDWebImageDownloaderOperation:负责真正的图片下载请求,一个自定义的并行Operation子类
  • SDImageCache:负责SDWebImage的缓存工作,是一个单例对象
  • SDWebImageManager:是总的管理类,维护了一个SDWebImageDownloader实例和一个SDImageCache实例,是下载与缓存的桥梁
  • SDWebImagePrefetcher:负责图片的预取
  • SDWebImageDecoder:负责图片的解压缩
  • UIImageVuew+WebCache:和其他的扩展都是与用户直接打交道的

概念框架

在这里插入图片描述
在这里插入图片描述

  • UIImageView+WebCache和UIButton+WebCache直接为表层的UIKit框架提供接口
  • SDWebImageManager负责处理和协调SDWebImageDownloader和SDWebImagecache,并和UIKit层进行交互
  • SDWebImageDownloaderOperation真正执行下载请求,最底层的两个类为高层抽象提供支持

源码

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

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

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

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

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

他们都调用了同一个方法

//UIImageView+WebCache

  • (void)sd_setImageWithURL:(nullable NSURL *)url
    placeholderImage:(nullable UIImage *)placeholder
    options:(SDWebImageOptions)options
    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
    completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock {
    [self sd_internalSetImageWithURL:url
                    placeholderImage:placeholder
                             options:options
                        operationKey:nil
                       setImageBlock:nil
                            progress:progressBlock
                           completed:completedBlock];
}

UIView+WebCache

- (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 {
    return [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options operationKey:operationKey setImageBlock:setImageBlock progress:progressBlock completed:completedBlock context:nil];
}
//url。图像的url
//placeholder    最初要设置的图像,直到图像请求完成
//options 下载图像时要使用的选项
//context   上下文包含用于执行指定更改或过程的不同选项
//setImageBlock   块用于自定义设置图像代码
//progressBlock   下载图像时调用的块。进度块是在后台队列上执行的
//这个块没有返回值,并以请求的UIImage作为第一个参数,NSData表示作为第二个参数。如果发生错误,image参数为nil,第三个参数可能包含一个NSError。第四个参数是一个“SDImageCacheType”enum,表示图像是从本地缓存,内存缓存韩式网络检测到的。第五个参数通常是“YES”,但是,如果你提供SDWebImageAvoidAutoSetImage与SDWebImageProgressiveLoad选项,以启用渐进下载和设置自己的映像。因此,对部分图像重复调用该块。当图像完全下载后,最后一次调用该块,并将最后一个参数设置为YES。
//最后一个参数是原始图像URL。
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                           context:(nullable SDWebImageContext *)context
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDImageLoaderProgressBlock)progressBlock
                         completed:(nullable SDInternalCompletionBlock)completedBlock {
    if (context) {
        //获取枚举选项
        // copy to avoid mutable object
        // 复制以避免可变对象
        context = [context copy];
    } else {
        context = [NSDictionary dictionary];
    }
    //获取一个有效的操作的对应键值,这里获取的是"设置图片操作的键"
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
    //如果传递的context中没有该图片操作键,就新建一个该类的操作键赋给context
    if (!validOperationKey) {
        // pass through the operation key to downstream, which can used for tracing operation or image view class
        // 将操作键传递给下游,可用于跟踪操作或图像视图类
        validOperationKey = NSStringFromClass([self class]);
        SDWebImageMutableContext *mutableContext = [context mutableCopy];
        mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
        context = [mutableContext copy];
    }
    
    //将当前操作键赋值给 sd_latestOperationKey属性,操作键用于标识一个视图实例的不同查询(如UIButton)。
    self.sd_latestOperationKey = validOperationKey;
    //下面这行代码是保证没有当前正在进行的异步下载操作, 使它不会与即将进行的操作发生冲突
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    //将传递过来的url赋值给 sd_imageURL属性
    //注意,由于类别的限制,如果直接使用setImage:,这个属性可能会不同步。
    self.sd_imageURL = url;
    
    //添加临时的占位图(在不延迟添加占位图的option下)
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        });
    }
    
    if (url) {
        // reset the progress
        //重置进程
        NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
        if (imageProgress) {
            imageProgress.totalUnitCount = 0;
            imageProgress.completedUnitCount = 0;
        }
        
#if SD_UIKIT || SD_MAC     //环境判断
        // check and start image indicator
        // 检查并启动图像指示灯
        [self sd_startImageIndicator]; //开启图像指示器
        
        id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
        //self.sd_imageIndicator
        //图像加载期间的图像指示器。如果你不需要指示器,请指定nil。默认为零
        //这个设置将移除旧的指示器视图,并将新的指示器视图添加到当前视图的子视图中。
        //@注意,因为这是UI相关的,你应该只从主队列访问。

#endif  //环境判断
        //从context字典中获取该键对应的数据
        //SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager";
        SDWebImageManager *manager = context[SDWebImageContextCustomManager];
        //如果不存在,就新创建一个单例
        if (!manager) {
            manager = [SDWebImageManager sharedManager];
        } else {
            // remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager)
            // 删除manager以避免循环引用(manger -> loader -> operation -> context -> manager)
            SDWebImageMutableContext *mutableContext = [context mutableCopy];
            mutableContext[SDWebImageContextCustomManager] = nil;
            context = [mutableContext copy];
        }
        
        //--------------SDImageLoaderProgressBlock-----------------//
        SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
            if (imageProgress) {
                //项目总数
                imageProgress.totalUnitCount = expectedSize;
                //已完成数
                imageProgress.completedUnitCount = receivedSize;
            }
#if SD_UIKIT || SD_MAC//环境判断
            //重写并更新进度
            if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) {
                double progress = 0;
                if (expectedSize != 0) {
                    progress = (double)receivedSize / expectedSize;
                }
                progress = MAX(MIN(progress, 1), 0); // 0.0 - 1.0
                //在主线程不等待执行
                dispatch_async(dispatch_get_main_queue(), ^{
                    [imageIndicator updateIndicatorProgress:progress];
                });
            }
#endif      //环境判断
            if (progressBlock) {
                progressBlock(receivedSize, expectedSize, targetURL);
            }
        };
        //--------------SDImageLoaderProgressBlock-----------------//
        @weakify(self);
        //SDWebImageManager下载图片
        id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            @strongify(self);
            //如果不存在就直接,结束函数了。
            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
            //是否应该调用CompletedBlock,通过options和状态的&运算决定
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            //是否应该不设置Image,通过options和状态的&运算决定
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            //调用CompletedBlock的Block
            SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
                if (!self) { return; }
                if (!shouldNotSetImage) {
                    [self sd_setNeedsLayout];
                }
                //判断是否需要调用completedBlock
                if (completedBlock && shouldCallCompletedBlock) {
                    //调用completedBlock,image,而且不自动替换 placeholder image
                    completedBlock(image, data, error, cacheType, finished, url);
                }
            };
            
            // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set;我们得到了一个图像,但是SDWebImageAvoidAutoSetImage标志被设置了
            // OR
            // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set;我们没有得到图像,SDWebImageDelayPlaceholder没有设置
            if (shouldNotSetImage) {     //如果应该不设置图像
                //主线程不等待安全的调用上述创建的callCompletedBlockClojure
                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
            // 检查是否使用图像过渡
            SDWebImageTransition *transition = nil;
            BOOL shouldUseTransition = NO;//是否使用过滤属性
            if (options & SDWebImageForceTransition) {//强制转换
                // 总是
                shouldUseTransition = YES;
            } else if (cacheType == SDImageCacheTypeNone) { //类型不可用
                // 从网络
                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 : 保证block能在主线程安全进行,不等待
            dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
                //马上替换 placeholder image
                [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
                //马上替换 placeholder image
                [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif
                //使用上述创建的block变量,调用CompletedBlock
                callCompletedBlockClojure();
            });
        }];
        //----------operation----------//
        //在操作缓存字典(operationDictionary)里添加operation,表示当前的操作正在进行
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
        //-------------------------url存在---------------------------//
    } else {
//-------------------------url不存在---------------------------//
#if SD_UIKIT || SD_MAC
        //url不存在立马停止图像指示器
        [self sd_stopImageIndicator];
#endif
        //如果url不存在,就在completedBlock里传入error(url为空)
        dispatch_main_async_safe(^{
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
                completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
            }
        });
    }
    //-------------------------url不存在---------------------------//
}

取消之前下载操作

    if (context) {
        //获取枚举选项
        // copy to avoid mutable object
        // 复制以避免可变对象
        context = [context copy];
    } else {
        context = [NSDictionary dictionary];
    }
    //获取一个有效的操作的对应键值,这里获取的是"设置图片操作的键"
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
    //如果传递的context中没有该图片操作键,就新建一个该类的操作键赋给context
    if (!validOperationKey) {
        // pass through the operation key to downstream, which can used for tracing operation or image view class
        // 将操作键传递给下游,可用于跟踪操作或图像视图类
        validOperationKey = NSStringFromClass([self class]);
        SDWebImageMutableContext *mutableContext = [context mutableCopy];
        mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
        context = [mutableContext copy];
    }
    
    //将当前操作键赋值给 sd_latestOperationKey属性,操作键用于标识一个视图实例的不同查询(如UIButton)。
    self.sd_latestOperationKey = validOperationKey;
    //下面这行代码是保证没有当前正在进行的异步下载操作, 使它不会与即将进行的操作发生冲突
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];


其中在这一部分调用了UIView+WebCacheOperation中的方法,我们来看看它是怎么实现的:

//设置图像加载操作,使用一个字典存储key和对应的操作
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
    //key存在
    if (key) {
        //取消当前UIView和key的所有操作,key——标识操作的键
        [self sd_cancelImageLoadOperationWithKey:key];
        //操作存在
        if (operation) {
            //获取operationDictionary字典
            SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
            //创建一个互斥锁,保证此时没有其它线程对self对象进行修改,然后再添加key和对应的operation(操作)
            @synchronized (self) {
                [operationDictionary setObject:operation forKey:key];
            }
        }
    }
}

- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    if (key) {
        // Cancel in progress downloader from queue
        SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
        id<SDWebImageOperation> operation;
        
        @synchronized (self) {
            operation = [operationDictionary objectForKey:key];
        }
        if (operation) {
            if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
                [operation cancel];
            }
            @synchronized (self) {
                [operationDictionary removeObjectForKey:key];
            }
        }
    }
}

实际上,所有的操作都是由一个实际上,所有的操作都是由一个operationDictionary字典维护的,执行新的操作之前,cancel所有的operation。
为什么不直接在UIImageView+WebCache里直接关联这个对象呢?我觉得这里作者应该是遵从面向对象的单一职责原则(SRP:Single responsibility principle),就连类都要履行这个职责,何况分类呢?这里作者专门创造一个分类UIView+WebCacheOperation来管理操作缓存(字典)。

设置占位图

//2.设置占位图
    //将传递过来的url赋值给 sd_imageURL属性
    //注意,由于类别的限制,如果直接使用setImage:,这个属性可能会不同步。
    self.sd_imageURL = url;
    
    //添加临时的占位图(在不延迟添加占位图的option下)
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        });
    }

判断URL是否合法

    //3.判断URL是否合法
    if (url) {
        // reset the progress
        //重置进程
        NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
        if (imageProgress) {
            imageProgress.totalUnitCount = 0;
            imageProgress.completedUnitCount = 0;
        }
        
#if SD_UIKIT || SD_MAC     //环境判断
        // check and start image indicator
        // 检查并启动图像指示灯
        [self sd_startImageIndicator]; //开启图像指示器
        
        id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
        //self.sd_imageIndicator
        //图像加载期间的图像指示器。如果你不需要指示器,请指定nil。默认为零
        //这个设置将移除旧的指示器视图,并将新的指示器视图添加到当前视图的子视图中。
        //@注意,因为这是UI相关的,你应该只从主队列访问。

#endif  //环境判断
        //从context字典中获取该键对应的数据
        //SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager";
        SDWebImageManager *manager = context[SDWebImageContextCustomManager];
        //如果不存在,就新创建一个单例
        if (!manager) {
            manager = [SDWebImageManager sharedManager];
        } else {
            // remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager)
            // 删除manager以避免循环引用(manger -> loader -> operation -> context -> manager)
            SDWebImageMutableContext *mutableContext = [context mutableCopy];
            mutableContext[SDWebImageContextCustomManager] = nil;
            context = [mutableContext copy];
        }
        
        //--------------SDImageLoaderProgressBlock-----------------//
        SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
            if (imageProgress) {
                //项目总数
                imageProgress.totalUnitCount = expectedSize;
                //已完成数
                imageProgress.completedUnitCount = receivedSize;
            }
#if SD_UIKIT || SD_MAC//环境判断
            //重写并更新进度
            if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) {
                double progress = 0;
                if (expectedSize != 0) {
                    progress = (double)receivedSize / expectedSize;
                }
                progress = MAX(MIN(progress, 1), 0); // 0.0 - 1.0
                //在主线程不等待执行
                dispatch_async(dispatch_get_main_queue(), ^{
                    [imageIndicator updateIndicatorProgress:progress];
                });
            }
#endif      //环境判断
            if (progressBlock) {
                progressBlock(receivedSize, expectedSize, targetURL);
            }
        };
        //--------------SDImageLoaderProgressBlock-----------------//
        @weakify(self);
        //SDWebImageManager下载图片
        id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            @strongify(self);
            //如果不存在就直接,结束函数了。
            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
            //是否应该调用CompletedBlock,通过options和状态的&运算决定
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            //是否应该不设置Image,通过options和状态的&运算决定
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            //调用CompletedBlock的Block
            SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
                if (!self) { return; }
                if (!shouldNotSetImage) {
                    [self sd_setNeedsLayout];
                }
                //判断是否需要调用completedBlock
                if (completedBlock && shouldCallCompletedBlock) {
                    //调用completedBlock,image,而且不自动替换 placeholder image
                    completedBlock(image, data, error, cacheType, finished, url);
                }
            };
            
            // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set;我们得到了一个图像,但是SDWebImageAvoidAutoSetImage标志被设置了
            // OR
            // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set;我们没有得到图像,SDWebImageDelayPlaceholder没有设置
            if (shouldNotSetImage) {     //如果应该不设置图像
                //主线程不等待安全的调用上述创建的callCompletedBlockClojure
                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
            // 检查是否使用图像过渡
            SDWebImageTransition *transition = nil;
            BOOL shouldUseTransition = NO;//是否使用过滤属性
            if (options & SDWebImageForceTransition) {//强制转换
                // 总是
                shouldUseTransition = YES;
            } else if (cacheType == SDImageCacheTypeNone) { //类型不可用
                // 从网络
                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 : 保证block能在主线程安全进行,不等待
            dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
                //马上替换 placeholder image
                [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
                //马上替换 placeholder image
                [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif
                //使用上述创建的block变量,调用CompletedBlock
                callCompletedBlockClojure();
            });
        }];
        //----------operation----------//
        //在操作缓存字典(operationDictionary)里添加operation,表示当前的操作正在进行
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
        //-------------------------url存在---------------------------//
    } else {
//-------------------------url不存在---------------------------//
#if SD_UIKIT || SD_MAC
        //url不存在立马停止图像指示器
        [self sd_stopImageIndicator];
#endif
        //如果url不存在,就在completedBlock里传入error(url为空)
        dispatch_main_async_safe(^{
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
                completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
            }
        });
    }
    //-------------------------url不存在---------------------------//

工具层

上文提到过,SDWebImageManager同时管理SDImageCache和SDWebImageDownloader两个类,它是这一层的老大哥。在下载任务开始的时候,SDWebImageManager首先访问SDImageCache来查询是否存在缓存,如果有缓存,直接返回缓存的图片。如果没有缓存,就命令SDWebImageDownloader来下载图片,下载成功后,存入缓存,显示图片。以上是SDWebImageManager大致的工作流程。

直接找到loadImageWithURL方法,这个方法主要是对url的一些判断,context与options的预处理,内容如下:
a. 先判断url的可行性
b. 对context,options进行预处理,并放到result里面
c. 调用callCacheProcessForOperation 判断是否有缓存,如果有则进入ImageCache 拿到缓存数据,如果没有则进入callDownloadProcessForOperation 方法进一步判断如何下载
先看看这些步骤的源码,看完再看callCacheProcessForOperation做了些什么

SDWebImageManager

 // ==============  SDWebImageManager.m的扩展属性声明部分 ============== //
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache; //管理缓存
@property (strong, nonatomic, readwrite, nonnull) id<SDImageLoader> imageLoader; //下载器imageLoader;
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs; //记录失效url的名单
@property (strong, nonatomic, nonnull) NSMutableSet<SDWebImageCombinedOperation *> *runningOperations; //记录当前正在执行的操作
- (nullable SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                                 completed:(nonnull SDInternalCompletionBlock)completedBlock;
//如果缓存中没有图像,则在给定URL下载图像,否则返回缓存版本。
//参数一:url            图像的URL
//参数二:options        指定此请求使用的选项的掩码
//参数三:context        上下文包含不同的选项来执行指定的更改或流程,参见' SDWebImageContextOption '。这将保存'options'枚举不能保存的额外对象。
//参数四:progressBlock  下载图像时调用的块,进度块在后台队列上执行
//参数五:completedBlock 当操作完成时调用的块
     //必选参数
     /*   这个块没有返回值,并以请求的UIImage作为第一个参数,NSData表示作为第二个参数。
 	 *   如果发生错误,image参数为nil,第三个参数可能包含一个NSError。
 	 *   第四个参数是一个“SDImageCacheType”枚举,指示是否从本地缓存检索到图像或者从内存缓存或者网络。
 	 *   当使用SDWebImageProgressiveLoad选项并下载镜像时,第五个参数设置为NO。因此,对部分图像重复调用该块。当图像完全下载后,最后一次调用该块,并将最后一个参数设置为YES。
	 *   最后一个参数是原始图像URL
     //return 返回一个SDWebImageCombinedOperation的实例,你可以取消加载过程。


loadImageWithURL

- (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调用这个方法是没有意义的
    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.
    //很常见的错误是使用NSString对象而不是NSURL发送URL。由于一些奇怪的原因,Xcode不会对这种类型不匹配抛出任何警告。这里我们通过允许url作为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
    // 防止应用程序在参数类型错误时崩溃,比如发送NSNull而不是NSURL
    //如果url不是NSURL类那么就赋值为nil
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

    //SDWebImageCombinedOperation表示缓存和加载器操作的组合操作。您可以使用它来取消加载过程。
    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    //让上述变量的manager指向该类
    operation.manager = self;

    //判断该URL是否在黑名单中
    BOOL isFailedUrl = NO;
    //如果url存在
    if (url) {
        //线程等待self.failedURLsLock执行,知道其执行完毕
        SD_LOCK(self.failedURLsLock);
        //在self.failedURLs(失效的url名单)中查找是否有这个url
        isFailedUrl = [self.failedURLs containsObject:url];
        //使传入的信号量self.failedURLsLock的调用值加1,表示当前又加入一个线程等待其处理的信号量
        SD_UNLOCK(self.failedURLsLock);
    }

    //url的绝对字符串长度为0或者(不禁用黑名单)并且Url在黑名单内,发出警告提示
    //url有问题的话该函数就在这就结束了
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil";
        NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL;
        //调用completionBlock块,结束该函数
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] url:url];
        return operation;
    }

    //将这个操作加入进程
    SD_LOCK(self.runningOperationsLock);
    [self.runningOperations addObject:operation];
    SD_UNLOCK(self.runningOperationsLock);
    
    // Preprocess the options and context arg to decide the final the result for manager
    // 对操作和上下文参数进行预处理,为manager确定最终结果
        //返回已处理的操作结果,包括指定图像URL的操作和context
    SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
    
    // Start the entry to load image from cache
    // 启动从缓存加载图像的条目
    //查询普通缓存进程
    [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
    //返回这个新创建的操作变量
    return operation;
}

在最后两步调用了两个方法:- (SDWebImageOptionsResult *)processedResultForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context;(返回已处理的操作结果,包括指定图像URL的操作和content)和- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock;(查询普通缓存进程)这个函数中才开始真正下载此URL中的内容。

先来看第一个,返回SDWebImageOptionsResult:

这里先将任务加入到正在执行的列表里面,然后再对context进行预处理,源代码是没有对options进行说明处理的,然后将context跟options放入result里面。context的处理源代码就不贴出来了,大概就是对SDWebImageContextImageTransformer、SDWebImageContextCacheKeyFilter、SDWebImageContextCacheSerializer这3个进行一个判断,看是否有自定义的传过来,没有就用默认的。

- (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];
    }
    if (!result) {
        // Use default options result
        // 使用默认操作结果
        result = [[SDWebImageOptionsResult alloc] initWithOptions:options context:context];
    }
    
    return result;
}

第二个callCacheProcessForOperation的调用

这里主要是判断要到哪里去取数据,ImageCache,还是去下载,接下来就进入这个方法看一下。

这里主要是判断任务是否该走缓存查询,或者直接下载。如果是缓存查询,就进入SDImageCache里面进行缓存查询,且在此处理缓存结果的回调。否则就调用callDownloadProcessForOperation进入下一步判断。
①. 拿到imageCache,拿到缓存类型queryCacheType
②. 通过 options判断,走缓存还是下载。如果走缓存,则调用SDImageCache里面的queryImageForKey(开始进入SDImageCache的逻辑);如果走下载,则调用callDownloadProcessForOperation开始下载前的一些处理。

- (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;
    //判断SDWebImageContextImageCache对应内容是否符合协议SDImageCache
    if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
        imageCache = context[SDWebImageContextImageCache];
    } else {
        imageCache = self.imageCache;
    }
    
    // Get the query cache type
    // 获取查询缓存类型,存在的话就将其转换为integer类型数据
    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和context结合起来转换成一个cache的key
        NSString *key = [self cacheKeyForURL:url context:context];
        @weakify(operation);//定义一个弱引用self的变量,用于下面的block,避免循环引用
        //开始查询
        operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
            @strongify(operation);//定义一个强引用self的变量
            //如果operation存在并且operation被取消了
            if (!operation || operation.isCancelled) {
                // Image combined operation cancelled by user
                // 用户取消图像组合操作,调用CompletionBlock块,做出相应提示,说明用户取消了图像组合操作
                [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url];
                //将该operation安全移除进程,然后结束函数
                [self safelyRemoveOperationFromRunning:operation];
                return;
            } else if (context[SDWebImageContextImageTransformer] && !cachedImage) {
                //SDWebImageContextImageTransformer对应内容存在并且cachedImage不存在
                // 有机会查询原始缓存而不是下载,在原始缓存中查找
                // Have a chance to query original cache instead of downloading
                [self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
                return;
            }
            
            // Continue download process
            // 继续下载过程,这里的下载不同于下面的下载,这里的下载是从缓存中下载的,而不是url中
            [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
        }];
    } else {
        //不需要查询缓存
        // 继续下载过程,这里的下载是直接从url中下载的
        // Continue download process
        [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
    }
}


在这里调用了下载方法callDownloadProcessForOperation

- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                    url:(nonnull NSURL *)url
                                options:(SDWebImageOptions)options
                                context:(SDWebImageContext *)context
                            cachedImage:(nullable UIImage *)cachedImage
                             cachedData:(nullable NSData *)cachedData
                              cacheType:(SDImageCacheType)cacheType
                               progress:(nullable SDImageLoaderProgressBlock)progressBlock
                              completed:(nullable SDInternalCompletionBlock)completedBlock {
    // Grab the image loader to use
    // 获取要使用的图像加载器
    id<SDImageLoader> imageLoader;
    if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
        imageLoader = context[SDWebImageContextImageLoader];
    } else {
        imageLoader = self.imageLoader;
    }
    
    // Check whether we should download image from network
    // 检查是否需要从网络下载图像
    BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
    //(没有缓存图片) || (即使有缓存图片,也需要更新缓存图片)
    shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
    //(代理没有响应imageManager:shouldDownloadImageForURL:消息,默认返回yes,需要下载图片)|| (imageManager:shouldDownloadImageForURL:返回yes,需要下载图片)
    shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
    shouldDownload &= [imageLoader canRequestImageForURL:url];
    //如果应该从url下载图像
    if (shouldDownload) {
        //存在缓存图片 && 即使有缓存图片也要下载更新图片
        if (cachedImage && options & SDWebImageRefreshCached) {
            // 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.
            // 如果在缓存中找到了图像,但是sdwebimagerfreshcached被提供了,通知缓存的图像
            // 并尝试重新下载它,以便NSURLCache有机会从服务器刷新它。
            [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            // Pass the cached image to the image loader. The image loader should check whether the remote image is equal to the cached image.
            // 将缓存的图像传递给图像加载程序。图像加载程序应该检查远程图像是否等于缓存的图像。
            SDWebImageMutableContext *mutableContext;
            if (context) {
                mutableContext = [context mutableCopy];
            } else {
                mutableContext = [NSMutableDictionary dictionary];
            }
            mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
            context = [mutableContext copy];
        }
        
        @weakify(operation);
        // ========== operation.loaderOperation ========== //下载操作
        operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
            @strongify(operation);
            //如果操作不存在并且任务被取消,则什么都不做,避免和其他的completedBlock重复
            if (!operation || operation.isCancelled) {
                // 用户取消图像组合操作
                // Image combined operation cancelled by user
                [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] url:url];
                //存在缓存图片 && 即使有缓存图片也要下载更新图片 && 错误域相同 && error的代码为远程位置指定缓存的映像不被修改
            } else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
                // Image refresh hit the NSURLCache cache, do not call the completion block
                // 图像刷新击中NSURLCache缓存,不调用完成块
                //错误域相同 && 镜像加载操作在完成之前取消,在异步磁盘缓存查询期间,或在实际网络请求之前等待
            } else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) {
                // Download operation cancelled by user before sending the request, don't block failed URL
                // 下载操作被用户在发送请求前取消,不要阻止失败的URL
                [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
                //如果错误存在
            } else if (error) {
                [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
                //是否应该添加该url到错误名单中
                BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context];
                //在错误url名单中添加当前的url
                if (shouldBlockFailedURL) {
                    SD_LOCK(self.failedURLsLock);
                    [self.failedURLs addObject:url];
                    SD_UNLOCK(self.failedURLsLock);
                }
                //下载成功
            } else {
                //如果需要下载失败后重新下载,则将当前url从失败url名单里移除
                if ((options & SDWebImageRetryFailed)) {
                    SD_LOCK(self.failedURLsLock);
                    [self.failedURLs removeObject:url];
                    SD_UNLOCK(self.failedURLsLock);
                }
                // Continue store cache process
                // 继续存储缓存进程
                [self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
            }
            //如果完成,从当前运行的操作列表里移除当前操作
            if (finished) {
                [self safelyRemoveOperationFromRunning:operation];
            }
        }];
        // ========== operation.loaderOperation ========== //
        //存在缓存图片
    } else if (cachedImage) {
        //调用完成的block
        [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
        //删去当前的的下载操作(线程安全)
        [self safelyRemoveOperationFromRunning:operation];
        //没有缓存的图片,而且下载被代理终止了
    } else {
        // Image not in cache and download disallowed by delegate
        // 调用完成的block
        [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
        //删去当前的下载操作
        [self safelyRemoveOperationFromRunning:operation];
    }
}

SDImageCache

 // ==============  SDImageCache.m ============== //
@property (nonatomic, strong, readwrite, nonnull) id<SDMemoryCache> memoryCache;//内存缓存
@property (nonatomic, copy, readwrite, nonnull) NSString *diskCachePath;//磁盘缓存路径
@property (nonatomic, strong, nullable) dispatch_queue_t ioQueue;//ioQueue唯一子线程;
 (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
    //key不存在,结束查找函数
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    // 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];
    }
    
    // 如果存在,直接调用block,将image,data,CaheType传进去
    if (image) {
        //如果操作为解码获取第一帧产生静态图像
        if (options & SDImageCacheDecodeFirstFrameOnly) {
            // Ensure static image
            // 确保静态图像
            Class animatedImageClass = image.class;
            //如果图像是动画 || animatedImageClass是UIImage的实例 && animatedImageClass遵循SDAnimatedImage协议
            if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) {
#if SD_MAC
                image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
#else
                image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
#endif
            }
        //操作为确保总是与提供的类产生图像
        } else if (options & SDImageCacheMatchAnimatedImageClass) {
            // 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);
        }
        //因为图片有缓存可供使用,所以不用实例化NSOperation,直接返回nil
        return nil;
    }
    
    // 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
    // 检查是否需要同步查询磁盘
    //(在内存缓存命中) && memoryDataSync
    //(内存缓存错过) && diskDataSync
    BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
                                (!image && options & SDImageCacheQueryDiskDataSync));
    void(^queryDiskBlock)(void) =  ^{
        // 在用之前就判断operation是否被取消了,作者考虑的非常严谨,如果取消了就直接结束该函数了
        if (operation.isCancelled) {
            if (doneBlock) {
                doneBlock(nil, nil, SDImageCacheTypeNone);
            }
            return;
        }
        
        @autoreleasepool {
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage;
            if (image) {
                // 图像来自内存缓存,但需要图像数据
                // 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);
                }
                // 解码图像数据,只有在内存缓存错过
                // decode image data only if in-memory cache missed
                diskImage = [self diskImageForKey:key data:diskData options:options context:context];
                if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) {
                    // cost 被用来计算缓存中所有对象的代价。当内存受限或者所有缓存对象的总代价超过了最大允许的值时,缓存会移除其中的一些对象。
                    NSUInteger cost = diskImage.sd_memoryCost;
                    //存入内存缓存中
                    [self.memoryCache setObject:diskImage forKey:key cost:cost];
                }
            }
            
            if (doneBlock) {
                if (shouldQueryDiskSync) {
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                } else {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                    });
                }
            }
        }
    };
    
    // Query in ioQueue to keep IO-safe
    // 在唯一的子线程:self.ioQueue中查询,保证io安全
        //应该同步查询磁盘
    if (shouldQueryDiskSync) {
        //线程等待
        dispatch_sync(self.ioQueue, queryDiskBlock);
    } else {
        //线程不等待
        dispatch_async(self.ioQueue, queryDiskBlock);
    }
    
    return operation;
}

SDWebImageDownloader

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
    // URL将被用作回调字典的键,所以它不能为nil。如果为nil,立即调用没有图像或数据的已完成块。
    if (url == nil) {
        //如果completedBlock存在但是因为没有url所以就没办法访问,就直接提示错误就行
        if (completedBlock) {
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
            completedBlock(nil, nil, error, YES);
        }
        return nil;
    }
    
    SD_LOCK(self.operationsLock);
    //定义一个下载操作取消令牌
    id downloadOperationCancelToken;
    //当前下载操作中取出SDWebImageDownloaderOperation实例
    NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
    // There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`.
    // 有一种情况是,操作可能被标记为完成或取消,但没有从'self.URLOperations'中删除。
    //操作不存在||操作已完成||操作被取消
    if (!operation || operation.isFinished || operation.isCancelled) {
        //创建一个下载操作给operation
        //operation中保存了progressBlock和completedBlock
        operation = [self createDownloaderOperationWithUrl:url options:options context:context];
        //如果操作不存在,即上述创建失败,返回一个错误信息
        if (!operation) {
            SD_UNLOCK(self.operationsLock);
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
                completedBlock(nil, nil, error, YES);
            }
            return nil;
        }
        @weakify(self);
        //如果创建成功了,我们再继续完善其completionBlock代码块
        operation.completionBlock = ^{
            @strongify(self);
            if (!self) {
                return;
            }
            //在操作数组中删除此url的操作
            SD_LOCK(self.operationsLock);
            [self.URLOperations removeObjectForKey:url];
            SD_UNLOCK(self.operationsLock);
        };
        //将该url的下载操作赋值给self.URLOperations操作数组
        self.URLOperations[url] = operation;
        // Add the handlers before submitting to operation queue, avoid the race condition that operation finished before setting handlers.
        // 在提交到操作队列之前添加处理程序,避免操作在设置处理程序之前完成的竞态条件。
        //下载操作取消令牌
        downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
        // Add operation to operation queue only after all configuration done according to Apple's doc.
        // `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
        // 根据苹果文档完成所有配置后,才能将操作添加到操作队列中。
        // 'addOperation:'不会同步执行'operation.completionBlock',因此不会导致死锁。
        //将此下载操作添加到下载队列
        [self.downloadQueue addOperation:operation];
    } else {
        // When we reuse the download operation to attach more callbacks, there may be thread safe issue because the getter of callbacks may in another queue (decoding queue or delegate queue)
        // So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.
        // 当我们重用下载操作来附加更多的回调时,可能会有线程安全问题,因为回调的getter可能在另一个队列(解码队列或委托队列)
        // 所以我们锁定了这里的操作,在'SDWebImageDownloaderOperation'中,我们使用' @synchronized(self)'来确保这两个类之间的线程安全。

        @synchronized (operation) {
            //下载操作取消令牌
            downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
        }
        //如果该操作没有在执行
        if (!operation.isExecuting) {
            //给该操作赋相应的优先级操作
            if (options & SDWebImageDownloaderHighPriority) {
                operation.queuePriority = NSOperationQueuePriorityHigh;
            } else if (options & SDWebImageDownloaderLowPriority) {
                operation.queuePriority = NSOperationQueuePriorityLow;
            } else {
                operation.queuePriority = NSOperationQueuePriorityNormal;
            }
        }
    }
    SD_UNLOCK(self.operationsLock);
    
    //创建该下载的token,这里 downloadOperationCancelToken 默认是一个字典,存放 progressBlock 和 completedBlock
    //使用operation初始化该下载操作token
    SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
    token.url = url; //保存url
    token.request = operation.request; //保存请求
    token.downloadOperationCancelToken = downloadOperationCancelToken; //保存下载操作取消令牌
    
    return token;
}

我们发现这个方法在之前的方法里并没有调用过,我们在全局搜索下这个函数在哪用过呢?
在这个函数里:
这个方法是一个工具,用来生成SDWebImageDownloaderOptions对象,我们向前找发现在SDWebImageManager的callDownloadProcessForOperation方法里调了这个:

- (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDImageLoaderCompletedBlock)completedBlock {
    UIImage *cachedImage = context[SDWebImageContextLoaderCachedImage];
    
    SDWebImageDownloaderOptions downloaderOptions = 0;
    if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
    if (options & SDWebImageProgressiveLoad) downloaderOptions |= SDWebImageDownloaderProgressiveLoad;
    if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
    if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
    if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
    if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
    if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
    if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
    if (options & SDWebImageAvoidDecodeImage) downloaderOptions |= SDWebImageDownloaderAvoidDecodeImage;
    if (options & SDWebImageDecodeFirstFrameOnly) downloaderOptions |= SDWebImageDownloaderDecodeFirstFrameOnly;
    if (options & SDWebImagePreloadAllFrames) downloaderOptions |= SDWebImageDownloaderPreloadAllFrames;
    if (options & SDWebImageMatchAnimatedImageClass) downloaderOptions |= SDWebImageDownloaderMatchAnimatedImageClass;
    
    if (cachedImage && options & SDWebImageRefreshCached) {
        // force progressive off if image already cached but forced refreshing
        downloaderOptions &= ~SDWebImageDownloaderProgressiveLoad;
        // ignore image read from NSURLCache if image if cached but force refreshing
        downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
    }
    
    return [self downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:completedBlock];
}

然后我们再看downloadImageWithURL里用到的的方法,这个才是真正的下载:
通过这个函数createDownloaderOperationWithUrl赋值给了operation然后通过operation才赋值给了token

- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url
                                                                                  options:(SDWebImageDownloaderOptions)options
                                                                                  context:(nullable SDWebImageContext *)context {
    //创建一个等待时间
    NSTimeInterval timeoutInterval = self.config.downloadTimeout;
    if (timeoutInterval == 0.0) {
        timeoutInterval = 15.0;
    }
    
    // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
    // 为了防止潜在的重复缓存(NSURLCache + SDImageCache),我们禁用图像请求的缓存
    NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
    //创建下载请求
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
    mutableRequest.HTTPShouldHandleCookies = SD_OPTIONS_CONTAINS(options, SDWebImageDownloaderHandleCookies);
    mutableRequest.HTTPShouldUsePipelining = YES;
    //线程安全的创建一个请求头
    SD_LOCK(self.HTTPHeadersLock);
    mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
    SD_UNLOCK(self.HTTPHeadersLock);
    
    // Context Option
    // Context选项
    SDWebImageMutableContext *mutableContext;
    if (context) {
        mutableContext = [context mutableCopy];
    } else {
        mutableContext = [NSMutableDictionary dictionary];
    }
    
    // Request Modifier
    //请求修饰符,设置请求修饰符,在图像加载之前修改原始的下载请求。返回nil将取消下载请求。
    id<SDWebImageDownloaderRequestModifier> requestModifier;
    if ([context valueForKey:SDWebImageContextDownloadRequestModifier]) {
        requestModifier = [context valueForKey:SDWebImageContextDownloadRequestModifier];
    } else {
        //self.requestModifier默认为nil,表示不修改原始下载请求。
        requestModifier = self.requestModifier;
    }
    
    NSURLRequest *request;
    //如果请求修饰符存在
    if (requestModifier) {
        NSURLRequest *modifiedRequest = [requestModifier modifiedRequestWithRequest:[mutableRequest copy]];
        // If modified request is nil, early return
        // 如果修改请求为nil,则提前返回
        if (!modifiedRequest) {
            return nil;
        } else {
            request = [modifiedRequest copy];
        }
    } else {
        request = [mutableRequest copy];
    }
    // Response Modifier
    // 响应修饰符,设置响应修饰符来修改图像加载期间的原始下载响应。返回nil将标志当前下载已取消。
    id<SDWebImageDownloaderResponseModifier> responseModifier;
    if ([context valueForKey:SDWebImageContextDownloadResponseModifier]) {
        responseModifier = [context valueForKey:SDWebImageContextDownloadResponseModifier];
    } else {
        //self.responseModifier默认为nil,表示不修改原始下载响应。
        responseModifier = self.responseModifier;
    }
    //如果响应修饰存在
    if (responseModifier) {
        mutableContext[SDWebImageContextDownloadResponseModifier] = responseModifier;
    }
    // Decryptor
    // 图像解码,设置解密器对原始下载数据进行解密后再进行图像解码。返回nil将标志下载失败。
    id<SDWebImageDownloaderDecryptor> decryptor;
    if ([context valueForKey:SDWebImageContextDownloadDecryptor]) {
        decryptor = [context valueForKey:SDWebImageContextDownloadDecryptor];
    } else {
        //self.decryptor默认为nil,表示不修改原始下载数据。
        decryptor = self.decryptor;
    }
    //如果图像解码操作存在
    if (decryptor) {
        mutableContext[SDWebImageContextDownloadDecryptor] = decryptor;
    }
    
    context = [mutableContext copy];
    
    // Operation Class
    // 操作类
    Class operationClass = self.config.operationClass;
    //操作类存在 && 操作类是NSOperation的实例类 && 操作类遵守SDWebImageDownloaderOperation协议
    if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) {
        // Custom operation class
        // 自定义操作类(可以自行修改和定义)
    } else {
        //默认操作类
        operationClass = [SDWebImageDownloaderOperation class];
    }
    //创建下载操作:SDWebImageDownloaderOperation用于请求网络资源的操作,它是一个 NSOperation 的子类
    NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];
    
    //如果operation实现了setCredential:方法
    if ([operation respondsToSelector:@selector(setCredential:)]) {
        //url证书
        if (self.config.urlCredential) {
            operation.credential = self.config.urlCredential;
        } else if (self.config.username && self.config.password) {
            operation.credential = [NSURLCredential credentialWithUser:self.config.username password:self.config.password persistence:NSURLCredentialPersistenceForSession];
        }
    }
        
    //如果operation实现了setMinimumProgressInterval:方法
    if ([operation respondsToSelector:@selector(setMinimumProgressInterval:)]) {
        operation.minimumProgressInterval = MIN(MAX(self.config.minimumProgressInterval, 0), 1);
    }
    
    //设置该url的操作优先级
    if (options & SDWebImageDownloaderHighPriority) {
        operation.queuePriority = NSOperationQueuePriorityHigh;
    } else if (options & SDWebImageDownloaderLowPriority) {
        operation.queuePriority = NSOperationQueuePriorityLow;
    }
    
    if (self.config.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
        // Emulate LIFO execution order by systematically, each previous adding operation can dependency the new operation
        // This can gurantee the new operation to be execulated firstly, even if when some operations finished, meanwhile you appending new operations
        // Just make last added operation dependents new operation can not solve this problem. See test case #test15DownloaderLIFOExecutionOrder
        // 通过系统地模拟后进先出的执行顺序,前一个添加的操作可以依赖于新操作
        // 这样可以保证先执行新操作,即使有些操作完成了,同时又追加了新操作
        // 仅仅使上次添加的操作依赖于新的操作并不能解决这个问题。参见测试用例#test15DownloaderLIFOExecutionOrder

        for (NSOperation *pendingOperation in self.downloadQueue.operations) {
            [pendingOperation addDependency:operation];
        }
    }
    
    return operation;
}


SDWebImage总共嵌套了七八层类去完成操作,比之前学的JSONModel复杂很多,我看得还比较浅,在这补充下一些类的作用:

  • SDWebImageDownloaderOperation: 下载器的具体实现,继承自NSOperation,使用NSURLSession来执行具体的下载操作。
    SDWebImageDownloaderConfig: 下载器的配置类,用于配置下载选项,例如是否使用NSURLCache,是否允许蜂窝网络下载等。
  • SDWebImageManager: 负责图片下载和缓存的管理。
    SDImageCache: 图片缓存器,用于缓存已下载的图片数据,避免重复下载。
    SDImageCacheConfig: 缓存器的配置类,用于配置缓存选项,例如缓存路径、缓存时长等。
  • SDWebImageOperation: 图片加载器的具体实现,继承自NSOperation,用于加载和显示图片。
  • SDAnimatedImage: 用于播放动态图片。
  • SDAnimatedImageView: 用于显示动态图片的UIImageView子类。
  • SDWebImageCompat: 用于提供一些跨平台的API,例如判断图片类型、生成缩略图等。
  • UIImage+MultiFormat: 用于支持多种图片格式,例如WebP、GIF等。
  • SDWebImageDefine: 定义了一些宏,例如SD_LOCK、SD_UNLOCK等。
  • SDWebImageError: 定义了一些错误码和错误描述。
  • SDWebImagePrefetcher: 用于预下载图片数据,避免在需要显示图片时还需要进行下载操作。

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

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

相关文章

XLA IR:HLO、LHLO、MHLO和LMHLO

XLA IR&#xff1a;HLO、LHLO、MHLO和LMHLO HLOLHLOMHLOLMHLOXLA IR 总结HLO->LMHLO xla基本编译流程如下&#xff1a; HLO Optimization: 硬件无关优化和硬件相关优化LHLO Codegen: 算子向量化和llvmir的生成HLO&LHLO是XLA-HLO&#xff1b;MHLO&LMHLO是MLIR-HLO&…

队列的基本操作(C语言链表实现)初始化,入队,出队,销毁,读取数据

文章目录 前言一、队列基本变量的了解二、队列的基本操作2.1队列的初始化&#xff08;QueueInit&#xff09;2.2入队&#xff08;QueuePush&#xff09;2.3判断是否为空队&#xff08;QueueEmpty&#xff09;2.4出队&#xff08;QueuePop&#xff09;2.5队列的队头数据&#xf…

分享BigDecimal金额计算的4种方式

基本介绍 金额计算这块通常都是基于2位小数的四舍五入&#xff0c;如果是自己的系统内部功能金额位数一般都是固定的&#xff0c;在正常的加减乘除运算逻辑都是保留同样位数的&#xff0c;但是乘法和除法相对比较特殊&#xff0c;在计算小数的部分可能会计算出更多位数的小数点…

【微服务】搭建Consul集群服务和Consul配置中心

文章目录 一、传统配置文件的弊端二、微服务配置中心三、主流的配置中心四、Consul 配置操作1.添加配置信息2.获取配置信息 五、单点服务器Consul集群 一、传统配置文件的弊端 静态化配置&#xff0c;例如env文件配置文件无法区分环境配置文件过于分散历史版本无法查看 配置中…

5.5、线程池同步机制类封装及线程池实现

代码地址 5.5、线程池同步机制类封装及线程池实现 1.线程池2.代码实现①锁Ⅰ、locker.hⅡ、locker.cpp ②条件变量Ⅰ、cond.hⅡ、cond.cpp ③信号量Ⅰ、sem.hⅡ、sem.cpp ④线程池Ⅰ、threadpool.hⅡ、threadpool.cpp 1.线程池 线程池是由服务器预先创建的一组子线程&#xf…

行为识别预警系统 opencv

行为识别预警系统通过pythonopencv网络模型技术&#xff0c;行为识别预警系统对现场画面中人的行为进行智能分析&#xff0c;发现违规行为自动抓拍告警&#xff0c;同步回传后台存档提醒值班人员及时处理。OpenCV的全称是Open Source Computer Vision Library&#xff0c;是一个…

CVE漏洞复现-CVE-2019-5736 Docker逃逸

CVE-2019-5736 Docker逃逸 Docker是什么&#xff1f; Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有…

JAVA实现AES加密、解密附代码

大家好&#xff0c;今天我们一起来学习 Java中的加密解密技术&#xff0c; java是一门面向对象的语言&#xff0c;使用它的开发人员都是非常聪明的人&#xff0c;他们对数据的安全性要求很高。在实际的应用中&#xff0c;我们都需要对数据进行加密、解密处理&#xff0c;这在 j…

【数据结构】第十三站:排序(上)

本文目录 一、排序概念二、插入排序1.插入排序的基本思想2.算法实现3.时间复杂度分析 三、希尔排序1. 希尔排序的思想2.希尔排序的代码实现3.希尔排序和插入排序的性能测试比较4.希尔排序的时间复杂度 四、直接选择排序1.基本思想2.直接选择排序的步骤3.直接选择排序的代码实现…

接口自动化测试用例如何设计?

说到自动化测试&#xff0c;或者说接口自动化测试&#xff0c;多数人的第一反应是该用什么工具&#xff0c;比如&#xff1a;Python Requests、Java HttpClient、Apifox、MeterSphere、自研的自动化平台等。大家似乎更关注的是哪个工具更优秀&#xff0c;甚至出现“ 做平台的 &…

音视频开发开发核心知识+小白入门必看基础知识

音视频开发是一个广泛的领域&#xff0c;它涉及到多个技术领域&#xff0c;包括音频编解码、视频编解码、媒体容器格式、流媒体传输、音视频处理等。以下是音视频开发的一些基础知识&#xff1a; 音频编解码器&#xff1a;音频编解码器是将数字音频信号编码成一种压缩格式&…

什么原因会导致香港轻量云服务器运行缓慢?

对于外贸企业主来说&#xff0c;想要为自己的网站选择一个可靠的主机供应商&#xff0c;并考虑使用香港轻量云服务器&#xff0c;则本文是必读的。本文将探讨租用香港轻量云服务器后遇到的运行缓慢的问题。下文这些因素被视为其中的主要因素。仔细分析它们中的每一个将帮助您确…

华为OD机试真题(Java),5键键盘的输出(100%通过+复盘思路)

一、题目描述 有一个特殊的5键键盘&#xff0c;上面有a&#xff0c;ctrl-c&#xff0c;ctrl-x&#xff0c;ctrl-v&#xff0c;ctrl-a五个键。 a键在屏幕上输出一个字母a&#xff1b;ctrl-c将当前选择的字母复制到剪贴板&#xff1b;ctrl-x将当前选择的字母复制到剪贴板&#…

python 打包新方案

首先是打包一个最简单的python 代码使用 pyinstaller import os #直接读取文件获得python.exe 路径 # 待执行python路径 with open("path_run.txt","r",encoding"utf-8") as f:python_exe,pyf.readlines() os.system("{} {}".format(p…

关于Acunetix(AWVS)激活时候失败可能的方法

关于Acunetix(AWVS)激活的时候失败&#xff0c;可能的解决方法 如果自己的激活包大概率没有问题&#xff0c;但是自己却激活不了 可以在服务中关闭所有的进程&#xff0c;再进行文件替换 方法如下&#xff1a; 1.先卸载文件&#xff0c;卸载的干干净净 2.正常安装 3.安装完毕…

cuDNN 的初始设计

cuDNN V1.0在2014年的发布,并集成到 Caffe、Paddle 等深度学习框架中。论文 cuDNN: Efficient Primitives for Deep Learning 介绍了 NVIDIA 对于该库的设计和实现。近十年间&#xff0c;NVIDIA 迭代推出了8代架构&#xff0c;cuDNN 也更新到 8.9。硬件上引入了 Tensor Core&am…

超级详细的 VirtualBox 虚拟机安装 及入门教程

一、前言 虚拟机&#xff08;Virtual Machine&#xff09;指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。在实体计算机中能够完成的工作在虚拟机中都能够实现。 虚拟机是在一些开发测试工作中常常需要用到的功能&#xff0c;常见的虚拟机…

数学建模算法汇总(全网最全!含matlab案例代码)

数学建模常用的算法分类 全国大学生数学建模竞赛中&#xff0c;常见的算法模型有以下30种&#xff1a; 最小二乘法数值分析方法图论算法线性规划整数规划动态规划贪心算法分支定界法蒙特卡洛方法随机游走算法遗传算法粒子群算法神经网络算法人工智能算法模糊数学时间序列分析马…

【对比度增强】Learning Tone Curves for Local Image Enhancement(LTMNet)

文章目录 0. 前言1. 理解1.1 整体框架1.2 网络结构1.3 细节 2. 亮点3. 总结 0. 前言 LTMNet这篇文章借鉴了CLAHE算法&#xff0c;所有步骤与CLAHE一致&#xff0c;不同之处在于LTMNet中局部映射曲线是通过CNN预测得到&#xff0c;而CLAHE中是通过直方图均衡化而得。关于CLAHE&…

MySQL_第10章_创建和管理表

第10章_创建和管理表 讲师&#xff1a;尚硅谷 - 宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a; http://www.atguigu.com 1. 基础知识 1.1 一条数据存储的过程 存储数据是处理数据的第一步 。只有正确地把数据存储起来&#xff0c;我们才能…