• iOS——SDWebImage源码解析


    SDWebImage概述

    SDWebImage是一个开源的第三方库,它提供了UIImageView的一个分类,以支持从远程服务器下载并缓存图片的功能。它具有以下功能:

    1.一个异步的图片加载器。

    2.一个异步的内存+磁盘图片缓存

    3.支持GIF、WebP图片

    4.后台图片解压缩处理

    5.确保同一个URL的图片不被多次下载

    6.确保非法的URL不会被反复加载

    7.确保下载及缓存时,主线程不被阻塞。

    这个框架的核心类是SDWebImageManger,在外部有UIImageView+WebCacheUIButton+WebCache 为下载图片的操作提供接口。内部有SDWebImageManger负责处理和协调 SDWebImageDownloader 和 SDWebImageCache:SDWebImageDownloader负责具体的下载任务,SDWebImageCache负责关于缓存的工作:添加,删除,查询缓存。

    SDWebImage大致流程如下图所示:
    在这里插入图片描述

    从这个流程图里可以大致看出,该框架分为两个层:UIKit层(负责接收下载参数)和工具层(负责下载操作和缓存)。

    UIKit层

    该框架最外层的类是UIImageView +WebCache,我们将图片的URL,占位图片直接给这个类。下面是这个类的公共接口:

    @implementation UIImageView (WebCache)
    
    - (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];
    }
    
    - (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];
    }
    
    - (void)sd_setImageWithPreviousCachedImageWithURL:(nullable NSURL *)url
                                     placeholderImage:(nullable UIImage *)placeholder
                                              options:(SDWebImageOptions)options
                                             progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                            completed:(nullable SDExternalCompletionBlock)completedBlock {
        NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:url];
        UIImage *lastPreviousCachedImage = [[SDImageCache sharedImageCache] imageFromCacheForKey:key];
        
        [self sd_setImageWithURL:url placeholderImage:lastPreviousCachedImage ?: placeholder options:options progress:progressBlock completed:completedBlock];    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    点击这些方法,最后都会走到:

    - (void)sd_setImageWithURL:(nullable NSURL *)url
              placeholderImage:(nullable UIImage *)placeholder
                       options:(SDWebImageOptions)options
                      progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                     completed:(nullable SDExternalCompletionBlock)completedBlock;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    继续点击这个方法,可以看到这个方法调用"UIView+WebCache"的方法:

    • 为什么不是UIImageView+WebCache而要上一层到UIView的分类里呢? 因为SDWebImage框架也支持UIButton的下载图片等方法,所以需要在它们的父类:UIView里面统一一个下载方法。
    - (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];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里看一下这个方法的实现:

    • 第一步:取消当前正在进行的异步下载,确保每个UIImageView对象中永远只存在一个operation,当前只允许一个图片网络请求,该operation负责从缓存中获取image或者是重新下载image。具体执行代码是:
    // 每个UIImageView对象只有一个operation
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    // 取消之前的下载任务
        [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    
    • 1
    • 2
    • 3
    • 4

    看下这个取消下载方法,定义在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        
            // typedef NSMapTable<NSString *, id<SDWebImageOperation>> SDOperationsDictionary;
     // 这里就是一个字典存储了operation
            SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];// 获取添加在UIView的自定义属性
            id<SDWebImageOperation> operation;
            
            @synchronized (self) {
                operation = [operationDictionary objectForKey:key];
            }
            if (operation) {
                if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
                    [operation cancel];
                }
                @synchronized (self) {
                    [operationDictionary removeObjectForKey:key];
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

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

    • 第二步:占位图策略
      作为图片下载完成之前的替代图片。dispatch_main_async_safe: 保证在主线程安全执行。
    // 关联对象动态添加属性
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        // 获取上下文保存的调度组
        dispatch_group_t group = context[SDWebImageInternalSetImageGroupKey];
        // 判断是否设置了SDWebImageDelayPlaceholder这个选项,如果设置了则等到图片加载失败再设置占位图。
        if (!(options & SDWebImageDelayPlaceholder)) {
            if (group) {
            // 进入调度组
            // 源码只有dispatch_group_enter,没有dispatch_group_leave,这个可能要我们自己处理
                dispatch_group_enter(group);
            }
            dispatch_main_async_safe(^{
            // 设置占位图
                [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
            });
            
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    cacheType:图片缓存类型

    typedef NS_ENUM(NSInteger, SDImageCacheType) {
        /**
         * The image wasn't available the SDWebImage caches, but was downloaded from the web.
         *图片无法从SDWebImage缓存中获得,但已从网络下载
         */
        SDImageCacheTypeNone,
        /**
         * The image was obtained from the disk cache.
         * 从磁盘缓存中获取镜像。
         */
        SDImageCacheTypeDisk,
        /**
         * The image was obtained from the memory cache.
         * 从内存缓存中获取图像
         */
        SDImageCacheTypeMemory
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 第三步:判断url是否合法
      如果url合法,则进行图片下载操作,否则直接block回调失败
    if (url) {
    #if SD_UIKIT
            // check if activityView is enabled or not
            // 是否显示进度条
            if ([self sd_showActivityIndicatorView]) {
                [self sd_addActivityIndicator];
            }
    #endif
            
            // reset the progress
            // 初始化图片加载进度
    
            self.sd_imageProgress.totalUnitCount = 0;
            self.sd_imageProgress.completedUnitCount = 0;
            // 获取SDWebImageManager
            // 可以通过SDWebImageExternalCustomManagerKey设置自定义的SDWebImageManager,不然就使用默认的。
            SDWebImageManager *manager = [context objectForKey:SDWebImageExternalCustomManagerKey];
            if (!manager) {
                manager = [SDWebImageManager sharedManager];
            }
            // 设置图片加载进度回调
            // 这里把外部传进来的progressBlock包了一层,先更新UIImageView的sd_imageProgress,再继续往下传。
            __weak __typeof(self)wself = self;
            SDWebImageDownloaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
                wself.sd_imageProgress.totalUnitCount = expectedSize;
                wself.sd_imageProgress.completedUnitCount = receivedSize;
                if (progressBlock) {
                    progressBlock(receivedSize, expectedSize, targetURL);
                }
            };
            // 创建SDWebImageOperation并保存到sd_setImageLoadOperation
            id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                __strong __typeof (wself) sself = wself;
                //self是否被释放
    
                if (!sself) { return; }
    #if SD_UIKIT
    // 移除进度条
                [sself sd_removeActivityIndicator];
    #endif
                // if the progress not been updated, mark it to complete state
                // 如果进度没有更新,将其标记为完成状态
                if (finished && !error && sself.sd_imageProgress.totalUnitCount == 0 && sself.sd_imageProgress.completedUnitCount == 0) {
                    sself.sd_imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
                    sself.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
                }
                BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
                BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                          (!image && !(options & SDWebImageDelayPlaceholder)));
               // 设置图片加载失败的回调block
                SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
                    if (!sself) { return; }
                    if (!shouldNotSetImage) {
                        [sself sd_setNeedsLayout];
                    }
                    if (completedBlock && shouldCallCompletedBlock) {
                        completedBlock(image, error, cacheType, 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
                // case 1a:我们得到了一个图像,但是SDWebImageAvoidAutoSetImage标志被设置了// OR / case 1b:我们没有得到图像,SDWebImageDelayPlaceholder没有设置
                if (shouldNotSetImage) {
                    dispatch_main_async_safe(callCompletedBlockClojure);
                    return;
                }
                
                UIImage *targetImage = nil;
                NSData *targetData = nil;
                if (image) {
                    // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
                    // 我们得到了一个图像,SDWebImageAvoidAutoSetImage没有设置
                    targetImage = image;
                    targetData = data;
                } else if (options & SDWebImageDelayPlaceholder) {
                    // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                    // 我们没有图像,并且设置了SDWebImageDelayPlaceholder标志
                    targetImage = placeholder;
                    targetData = nil;
                }
                
    #if SD_UIKIT || SD_MAC
                // check whether we should use the image transition
    // 检查是否使用图像过渡
                SDWebImageTransition *transition = nil;
                if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
                    transition = sself.sd_imageTransition;
                }
    #endif
                dispatch_main_async_safe(^{
                    if (group) {
                        dispatch_group_enter(group);
                    }
    #if SD_UIKIT || SD_MAC
                    [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
    #else
                    [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
    #endif
                    if (group) {
                        // compatible code for FLAnimatedImage, because we assume completedBlock called after image was set. This will be removed in 5.x
                        BOOL shouldUseGroup = [objc_getAssociatedObject(group, &SDWebImageInternalSetImageGroupKey) boolValue];
                        if (shouldUseGroup) {
                            dispatch_group_notify(group, dispatch_get_main_queue(), callCompletedBlockClojure);
                        } else {
                            callCompletedBlockClojure();
                        }
                    } else {
                        callCompletedBlockClojure();
                    }
                });
            }];
            [self sd_setImageLoadOperation:operation forKey:validOperationKey];
        } 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);
                }
            });
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128

    工具层

    SDWebImageManager

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

    /* 它将异步下载器(SDWebImageDownloader)与图像缓存存储(SDImageCache)绑定。
    你可以使用这个类直接受益于web图像下载与缓存在另一个上下文,而不是UIView。*/
    @property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;//管理缓存
    @property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader //下载器*imageDownloader;
    @property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;//记录失效url的名单
    @property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations;//记录当前正在执行的操作
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    加载图片

    SDWebImageManager下载图片的方法只有一个:

    [SDWebImageManager.sharedManager loadImageWithURL:options:progress:completed:]
    
    
    • 1
    • 2

    这个方法就是SDImageCache类在工作:

    SDImageCache

     // ==============  SDImageCache.m ============== //
    @property (strong, nonatomic, nonnull) NSCache *memCache;//内存缓存
    @property (strong, nonatomic, nonnull) NSString *diskCachePath;//磁盘缓存路径
    @property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths;//
    @property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t //ioQueue唯一子线程;
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    下面具体看一下这个方法具体干了什么:

    • 对url进行处理,防止传进来的是NSString或NSNull
    // 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传递来避免这个错误。 
        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
        if (![url isKindOfClass:NSURL.class]) {
            url = nil;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 创建SDWebImageCombinedOperation
      继承自
    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
        operation.manager = self;
    
    
    • 1
    • 2
    • 3

    SDWebImageCombinedOperation这个类比较简单,只保存了下面这些属性:

    @property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
    @property (strong, nonatomic, nullable) SDWebImageDownloadToken *downloadToken;
    @property (strong, nonatomic, nullable) NSOperation *cacheOperation;
    @property (weak, nonatomic, nullable) SDWebImageManager *manager;
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    downloadToken和cacheOperation会在下面的流程中创建,用弱引用保存了SDWebImageManager是为了执行取消操作:[self.manager safelyRemoveOperationFromRunning:self];

    • 处理failedUrl
      failedURLsNSMutableSet<NSURL *>,里面保存了失败过的URL。如果url的地址为空,或者该URL请求失败过且没有设置重试SDWebImageRetryFailed选项,则直接直接调用完成。
    BOOL isFailedUrl = NO;
        if (url) {
            LOCK(self.failedURLsLock);
            isFailedUrl = [self.failedURLs containsObject:url];
            UNLOCK(self.failedURLsLock);
        }
    
        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;
        }
    // failedURLsLock是dispatch_semaphore_t,用来保证读写failedURLs的线程安全。
    // 接收一个信号和时间值,若信号的信号量为0,则会阻塞当前线程,直到信号量大于0或者经过输入的时间值;若信号量大于0,则会使信号量减1并返回,程序继续住下执行
    #define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    // 使信号量加1并返回
    #define UNLOCK(lock) dispatch_semaphore_signal(lock);
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 保存SDWebImageCombinedOperation,根据url生成cacheKey
    LOCK(self.runningOperationsLock);
        [self.runningOperations addObject:operation];
        UNLOCK(self.runningOperationsLock);
        NSString *key = [self cacheKeyForURL:url];
        // 我们可以设置@property (nonatomic, copy, nullable) SDWebImageCacheKeyFilterBlock cacheKeyFilter;来自定义cacheKey。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 解析缓存策略参数
      SDImageCacheQueryDataWhenInMemory:默认当在内存找到结果后就不再到磁盘缓存查找,设置这个属性后强制到磁盘缓存查找。
      SDImageCacheQueryDiskSync:默认情况下内存缓存同步,磁盘缓存异步,这个属性可以强制磁盘缓存同步。
      SDImageCacheScaleDownLargeImages:默认不会对图片进行缩放,这个属性会根据设备的内存对图片进行适当的缩放。
    SDImageCacheOptions cacheOptions = 0;
        if (options & SDWebImageQueryDataWhenInMemory) cacheOptions |= SDImageCacheQueryDataWhenInMemory;
        if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync;
        if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
        ......
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    从缓存加载

    SDImageCache创建cacheOperation

    // 弱引用避免保留环
     __weak SDWebImageCombinedOperation *weakOperation = operation;
     //在SDImageCache里查询是否存在缓存的图片
        operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            if (!strongOperation || strongOperation.isCancelled) {
                [self safelyRemoveOperationFromRunning:strongOperation];
                return;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里看一下是怎么查询的。- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock:
    首先判断key存在吗

    // 这里的key就是url返回的本地标识
    if (!key) {
    // 如果不存在回调block参数为空
    // typedef void(^SDCacheQueryCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType);
            if (doneBlock) {
                doneBlock(nil, nil, SDImageCacheTypeNone);
            }
            return nil;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    SDMemoryCache就是NSCache,当收到内存警告时会清除缓存。

    • NSCache
      NSCache是一个可变的集合类型,用于临时存放键值对,当资源不足时会被移除。
      另外还有两个关键属性:
    var countLimit: Int
    能最大存放对象的数量
    var totalCostLimit: Int
    在开始释放对象之前,cache最多能持有cost的数量
    
    需要注意的是totalCostLimit中的cost是根据setObject(ObjectType, forKey: KeyType, cost: Int)函数中的cost参数进行计算,也就是说cost需要使用者计算后存入。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果没有设置SDImageCacheQueryDataWhenInMemory属性,那找到图片后直接返回:

    // First check the in-memory cache...
        UIImage *image = [self imageFromMemoryCacheForKey:key];
        BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryDataWhenInMemory));
        if (shouldQueryMemoryOnly) {
            if (doneBlock) {
                doneBlock(image, nil, SDImageCacheTypeMemory);
            }
            return nil;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    创建一个NSOperation,也就是返回的cacheOperation
    它只是用来操控是否取消磁盘读取:

    //================查看磁盘的缓存=================//
    NSOperation *operation = [NSOperation new];
        void(^queryDiskBlock)(void) =  ^{
            if (operation.isCancelled) {
                // do not call the completion if cancelled
                return;
            }
            
            @autoreleasepool {
                NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
                UIImage *diskImage;
                SDImageCacheType cacheType = SDImageCacheTypeNone;
                
                if (image) {
                    // the image is from in-memory cache
                    // 判断图片是否在内存中
                    diskImage = image;
                    cacheType = SDImageCacheTypeMemory;
                } else if (diskData) {// 是否在磁盘中
                    cacheType = SDImageCacheTypeDisk;
                    // decode image data only if in-memory cache missed
                    // 解码图像数据
                    diskImage = [self diskImageForKey:key data:diskData options:options];
                    // self.config.shouldCacheImagesInMemory:是否使用内存缓存
                    if (diskImage && self.config.shouldCacheImagesInMemory) {
                    // cost 被用来计算缓存中所有对象的代价。当内存受限或者所有缓存对象的总代价超过了最大允许的值时,缓存会移除其中的一些对象。
                        NSUInteger cost = diskImage.sd_memoryCost;
                         //存入内存缓存中
                        [self.memCache setObject:diskImage forKey:key cost:cost];
                    }
                }
                
                if (doneBlock) {
                    if (options & SDImageCacheQueryDiskSync) {
                        doneBlock(diskImage, diskData, cacheType);
                    } else {
                        dispatch_async(dispatch_get_main_queue(), ^{
                            doneBlock(diskImage, diskData, cacheType);
                        });
                    }
                }
            }
        };
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    最后根据SDImageCacheQueryDiskSync来决定是异步执行还是同步执行。

    if (options & SDImageCacheQueryDiskSync) {
            queryDiskBlock();
        } else {
            dispatch_async(self.ioQueue, queryDiskBlock);
        }
        
        return operation;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    从缓存中查询看看接下来的操作

    __strong __typeof(weakOperation) strongOperation = weakOperation;
    // 如果执行过程中操作取消,安全移除操作
    // return 是跳出这个block,直接结束
    
            if (!strongOperation || strongOperation.isCancelled) {
                [self safelyRemoveOperationFromRunning:strongOperation];
                return;
            }
    // 1. 如果不存在缓存图片,或者需要刷新缓存 2. 代理可以响应方法,或者代理直接执行该方法,即从网络下载图片3.不能是可以防止从网络下载映像,只从内存读取这个标识。
       // 1 和 2 和3 是并且关系
       BOOL shouldDownload = (!(options & SDWebImageFromCacheOnly))
                && (!cachedImage || options & SDWebImageRefreshCached)
                && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    满足上面的三点就可以从网络下载。
    先介绍一下SDWebImageDownloader。

    SDWebImageDownloader

    属性:

     // ==============  SDWebImageDownloader.m ============== //
    @property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;//下载队列
    @property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;//最后添加的下载操作
    @property (assign, nonatomic, nullable) Class operationClass;//操作类
    @property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;//操作数组
    @property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;//HTTP请求头
    @property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;//用来阻塞前面的下载线程(串行化)
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    核心方法就是下载图片:

    - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                       options:(SDWebImageDownloaderOptions)options
                                                      progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                     completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    从网络加载

    首先确认了上面的三个条件,就算开始网络加载了,不过在调用上面的方法前,需要获取下载配置项downloaderOptions。

    if (cachedImage && options & SDWebImageRefreshCached) {
                    // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
                    // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
                    // 如果在缓存中找到了图片,但是设置了SDWebImageRefreshCached,因此要NSURLCache重新从服务器下载
               // 先调用completeBlock后续进行网络下载
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
                }
    
                // download if no image or requested to refresh anyway, and download allowed by delegate
                SDWebImageDownloaderOptions downloaderOptions = 0;
                if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
                if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
                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 (cachedImage && options & SDWebImageRefreshCached) {
                    // force progressive off if image already cached but forced refreshing
                    downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                    // ignore image read from NSURLCache if image if cached but force refreshing
                    downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
                }
                
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    SDWebImageDownloaderLowPriority:低优先级
    SDWebImageDownloaderProgressiveDownload:边加载变显示
    SDWebImageDownloaderUseNSURLCache:使用NSURLCache
    SDWebImageDownloaderContinueInBackground:App进入后台后继续下载图片
    SDWebImageDownloaderHandleCookies:处理NSHTTPCookieStore里面的Cookies
    SDWebImageDownloaderAllowInvalidSSLCertificates:忽略SSL证书
    SDWebImageDownloaderHighPriority:高优先级
    SDWebImageDownloaderScaleDownLargeImages:缩放图片
    如果图片已缓存,只是刷新缓存,则强制取消SDWebImageDownloaderProgressiveDownload 且忽略NSURLCache的缓存SDWebImageDownloaderIgnoreCachedResponse。

    • 开始调用SDWebImageDownloader下载:
    __weak typeof(strongOperation) weakSubOperation = strongOperation;
                strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                // 下载完成后的操作
     }];
    
    • 1
    • 2
    • 3
    • 4

    那么这个方法具体做了什么呢?点进去看一下
    a, 首先判断url是否为空:

    // URL将被用作回调字典的键,所以它不能为nil。如果为nil,立即调用没有图像或数据的已完成块。
    if (url == nil) {
            if (completedBlock != nil) {
                completedBlock(nil, nil, nil, NO);
            }
            return nil;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    b, 获取downloadOperation

    LOCK(self.operationsLock);
        id downloadOperationCancelToken;
        NSOperation<SDWebImageDownloaderOperationInterface> *operation = [self.URLOperations objectForKey:url];
    
    • 1
    • 2
    • 3

    c, 删除已经完成或被取消或者不存在的操作

    // 有一种情况是,操作可能被标记为完成或取消,但没有从' self. uroperations '中删除。
    if (!operation || operation.isFinished || operation.isCancelled) {
            operation = [self createDownloaderOperationWithUrl:url options:options];
            __weak typeof(self) wself = self;
            operation.completionBlock = ^{
                __strong typeof(wself) sself = wself;
                if (!sself) {
                    return;
                }
                LOCK(sself.operationsLock);
                [sself.URLOperations removeObjectForKey:url];
                UNLOCK(sself.operationsLock);
            };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    d, 设置progressBlock、completedBlock

    // 将url和操作标识存入操作字典中
    [self.URLOperations setObject:operation forKey:url];
    // if 操作
            // Add the handlers before submitting to operation queue, avoid the race condition that operation finished before setting handlers.
            downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
            //根据Apple文档完成所有配置后,才添加操作到操作队列。// ' addOperation: '不会同步执行' operation.completionBlock ',所以不会导致死锁。
            [self.downloadQueue addOperation:operation];
            // else 操作
            //当我们重用下载操作附加更多的回调,可能存在线程安全问题,因为getter的回调可能在另一个队列(解码队列或委托队列 // 我们这里锁操作,在“SDWebImageDownloaderOperation”,我们使用“@synchonzied(self),以确保这两个类之间的线程安全。
            @synchronized (operation) {
                downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    e, 生成SDWebImageDownloadToken

    SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
        token.downloadOperation = operation;
        token.url = url;
        token.downloadOperationCancelToken = downloadOperationCancelToken;
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    UE4 粒子特效基础学习 (03-制作上升光线特效)
    02-Vue按键修饰符,自定义指令
    用57000字讲解完2022年Java岗面试最常问的100道题,太肝了
    【华为OD机试真题 python】 分糖果II【2022 Q4 | 200分】
    【Linux网络】UdpSocket
    加密市场进入寒冬,是“天灾”还是“人祸”?
    Git远程仓库
    c++day2
    Android 发布Library 到远程maven 私服仓库(Nexus)
    notepad++下载 地址真实有效
  • 原文地址:https://blog.csdn.net/chabuduoxs/article/details/124896983