美文网首页
SDWebImage库源代码解析

SDWebImage库源代码解析

作者: percivals | 来源:发表于2021-03-01 09:46 被阅读0次

    功能简介

     1、一个添加了web图片加载和缓存管理的UIImageView分类
     2、一个异步图片下载器
     3、一个异步的内存加磁盘综合存储图片并且自动处理过期图片
     4、支持动态gif图
     5、支持webP格式的图片
     6、后台图片解压处理
     7、确保同样的图片url不会下载多次
     8、确保伪造的图片url不会重复尝试下载
     9、确保主线程不会阻塞
    

    SDWebImage加载图片流程

    第一步,取消异步下载

    **取消当前正在进行的异步下载,确保每个 UIImageView 对象中永远只存在一个 operation,当前只允许一个图片网络请求,该 operation 负责从缓存中获取 image 或者是重新下载 image **

    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    // 取消先前下载的任务
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    ... // 下载图片操作
    // 将生成的加载操作赋值给UIView的自定义属性
    [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    

    上述方法定义在UIView+WebCacheOperation类中

    - (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
        if (key) {
            // 如果之前已经有过该图片的下载操作,则取消之前的图片下载操作
            [self sd_cancelImageLoadOperationWithKey:key];
            if (operation) {
                SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
                @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]; // 获取添加在UIView的自定义属性
            id<SDWebImageOperation> operation;
    
            @synchronized (self) {
                operation = [operationDictionary objectForKey:key];
            }
            if (operation) {
                // 实现了SDWebImageOperation的协议
                if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
                    [operation cancel];
                }
                @synchronized (self) {
                    [operationDictionary removeObjectForKey:key];
                }
            }
        }
    }
    

    实际上,所有的操作都是由一个实际上,所有的操作都是由一个operationDictionary字典维护的,执行新的操作之前,cancel所有的operation。

    第二步 设置占位图 并判断url是否合法

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

    判断url是否合法

    if (url) {
         //下载图片操作
    } else {
        dispatch_main_async_safe(^{
    #if SD_UIKIT
            [self sd_removeActivityIndicator];
    #endif
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
    

    #### 第三步 获取图片

    获取图片的操作是由SDWebImageManager完成的,它是一个单例

    获取成功后根据枚举类型,判断是否需要设置图片,进行图片的设置操作

    ** SDWebImageManager ** 用于处理异步下载和图片缓存的类

    维护了一个SDImageCache实例和一个SDWebImageDownloader实例

    1. 获取图片方法
    - (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock;
    

    SDWebImageManager.h首先定义了一些枚举类型的SDWebImageOptions

    然后,声明了四个block

    //操作完成的回调,被上层的扩展调用。
    typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);
    
    //被SDWebImageManager调用。如果使用了SDWebImageProgressiveDownload标记,这个block可能会被重复调用,直到图片完全下载结束,finished=true,再最后调用一次这个block。
    typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);
    
    //SDWebImageManager每次把URL转换为cache key的时候调用,可以删除一些image URL中的动态部分。
    typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);
    
    typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull image, NSData * _Nullable data, NSURL * _Nullable imageURL);
    

    定义了SDWebImageManagerDelegate协议:

    @protocol SDWebImageManagerDelegate 
    
    @optional
    // 控制在cache中没有找到image时 是否应该去下载。
    - (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
    
    // 在下载之后,缓存之前转换图片。在全局队列中操作,不阻塞主线程
    - (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
    
    @end
    
    1. 判断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;
    }
    
    1. 集合failedURLs保存之前失败的urls,如果url为空或者url之前失败过且不采用重试策略,直接调用completedBlock返回错误。
    BOOL isFailedUrl = NO;
    if (url) {  // 判断url是否是失败过的url
        LOCK(self.failedURLsLock);
        isFailedUrl = [self.failedURLs containsObject:url];
        UNLOCK(self.failedURLsLock);
    }
    // 如果url为空或者url下载失败并且设置了不再重试
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
        return operation;
    }
    
    1. 保存operation
    LOCK(self.runningOperationsLock);
    [self.runningOperations addObject:operation];
    UNLOCK(self.runningOperationsLock);
    

    runningOperations是一个可变数组,保存所有的operation,主要用来监测是否有operation在执行,即判断running 状态。

    1. 查找是否有图片缓存
    - (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock
    

    通过cacheKeyForURL:context:方法生成独特的缓存key,用于查找缓存图片

    在SDImageCache类里进行图片缓存的查找

    - (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock
    

    通过queryCacheOperationForKey:options:context:cacheType:done: 查找缓存

    先进行imageFromMemoryCacheForKey内存图片的查找

    未找到则进行diskImageDataBySearchingAllPathsForKey沙盒图片的查找,找到需要将图片加入内存图片中,内存溢出则先清理一遍内存

    1. 如果没有在缓存中找到图片,或者不管是否找到图片,只要operation有SDWebImageRefreshCached标记,那么若SDWebImageManagerDelegate的shouldDownloadImageForURL方法返回true,即允许下载时,都使用SDWebImageManager的下载方法
    - (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
    

    下载成功后对图片做解码处理,回调返回。并对图片进行缓存,保存到内存中和本地沙盒中。

    总结:
        先把 placeholderImage 显示出来,再去缓存中查找图片,
    
        先根据一定方法转换生成缓存key来查内存图片,未找到就再根据缓存key生成指定路径,去沙盒中查找,如果找到则通过回调返回,如果是从沙盒中读取到了图片,将图片添加到内存缓存中,均未找到则需要去下载图片。
    
        下载成功后对图片做解码处理,回调返回。并对图片进行缓存,保存到内存中和本地沙盒中。
    
        如有需要,在展示图片前会做一些图片处理在进行展示,根据配置项来。
    

    参考:

    https://www.jianshu.com/p/ff9095de1753

    https://www.jianshu.com/p/5baeff2bc9d4

    相关文章

      网友评论

          本文标题:SDWebImage库源代码解析

          本文链接:https://www.haomeiwen.com/subject/wrvofltx.html