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

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

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

继续点击这个方法,可以看到这个方法调用"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];
}

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

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

实际上,所有的操作都是由一个实际上,所有的操作都是由一个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];
        });
        
    }

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
};
  • 第三步:判断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);
            }
        });
    }
}

工具层

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;//记录当前正在执行的操作


加载图片

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

[SDWebImageManager.sharedManager loadImageWithURL:options:progress:completed:]

这个方法就是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唯一子线程;


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

  • 对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;
    }
  • 创建SDWebImageCombinedOperation
    继承自
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    operation.manager = self;

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;


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;
    }
  • 保存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。
  • 解析缓存策略参数
    SDImageCacheQueryDataWhenInMemory:默认当在内存找到结果后就不再到磁盘缓存查找,设置这个属性后强制到磁盘缓存查找。
    SDImageCacheQueryDiskSync:默认情况下内存缓存同步,磁盘缓存异步,这个属性可以强制磁盘缓存同步。
    SDImageCacheScaleDownLargeImages:默认不会对图片进行缩放,这个属性会根据设备的内存对图片进行适当的缩放。
SDImageCacheOptions cacheOptions = 0;
    if (options & SDWebImageQueryDataWhenInMemory) cacheOptions |= SDImageCacheQueryDataWhenInMemory;
    if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync;
    if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
    ......

从缓存加载

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

这里看一下是怎么查询的。- (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;
    }

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

  • NSCache
    NSCache是一个可变的集合类型,用于临时存放键值对,当资源不足时会被移除。
    另外还有两个关键属性:
var countLimit: Int
能最大存放对象的数量
var totalCostLimit: Int
在开始释放对象之前,cache最多能持有cost的数量

需要注意的是totalCostLimit中的cost是根据setObject(ObjectType, forKey: KeyType, cost: Int)函数中的cost参数进行计算,也就是说cost需要使用者计算后存入。

如果没有设置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;
    }

创建一个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);
                    });
                }
            }
        }
    };
    

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

if (options & SDImageCacheQueryDiskSync) {
        queryDiskBlock();
    } else {
        dispatch_async(self.ioQueue, queryDiskBlock);
    }
    
    return operation;

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

__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]);

满足上面的三点就可以从网络下载。
先介绍一下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;//用来阻塞前面的下载线程(串行化)


核心方法就是下载图片:

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;

从网络加载

首先确认了上面的三个条件,就算开始网络加载了,不过在调用上面的方法前,需要获取下载配置项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;
            }
            

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) {
            // 下载完成后的操作
 }];

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

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

b, 获取downloadOperation

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

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

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

e, 生成SDWebImageDownloadToken

SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
    token.downloadOperation = operation;
    token.url = url;
    token.downloadOperationCancelToken = downloadOperationCancelToken;
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值