一行行看SDWebImage源码(最新版本v4.1.2)

官网 https://github.com/rs/SDWebImage 版本 4.1.2

简介

SDWebImage是iOS开发者经常使用的一个开源框架,这个框架的主要作用是:
一个异步下载图片并且支持缓存(也可自定义路径缓存).

与版本 3.8比较

SDWebImage 4.1.2 比3.8版本多了这几个文件。

  • NSImage+WebCache.h : 关于Image 的 gif 的判断
  • SDImageCacheConfig.h:关于缓存的配置(缓存到memory的时间, 是否缓存到 Memery)
  • UIView+WebCache.h:把之前 UIImageView+WebCache ,UIButton 通用的部分提取出来。把 UIImageView中的关于Activity indicator方法整理 到 UIView 里面,这样就拓展了 UIButton
  • UIView+WebCache.h中的下面这个方法,UIImageView &UIButton 都有用到。
- (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;

之前 UIButton 没有关于activity UIActivityIndicatorView 的方法. 并且sd_cancelCurrentImageLoad 在 UIImageView+WebCache 和 UIButton+WebCache中都有声明及实现。

- (void)sd_cancelCurrentImageLoad;

#if SD_UIKIT

#pragma mark - Activity indicator

/**
 *  Show activity UIActivityIndicatorView显示ActivityIndicatorView
 */
- (void)sd_setShowActivityIndicatorView:(BOOL)show;

/**
 *  set desired UIActivityIndicatorViewStyle
 *
 *  @param style The style of the UIActivityIndicatorView
 */
- (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style;

- (BOOL)sd_showActivityIndicatorView;
- (void)sd_addActivityIndicator;
- (void)sd_removeActivityIndicator;

常用方法:以[imageView sd_setImageWithURL:picURL placeholderImage:nil]; 为例

由于- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder 的实现直接调用的一个方法

- (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
                  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 中的方法为例:

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(nullable SDExternalCompletionBlock)completedBlock

现在开始一步一步看该方法的内部实现:

  • 首先先执行sd_cancelImageLoadOperationWithKey:
    //如果没有给定operationKey,那么获取类的名字,以它为 key
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    //移除UIView等拓展类(这里主要是UIImageView 和 UIButton)当前绑定的操作.当TableView的cell包含的UIImageView被重用的时候首先执行这一行代码,保证这个ImageView或者 button的下载和缓存组合操作都被取消
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];

接下来 看看sd_cancelImageLoadOperationWithKey:方法的实现

下面我们先来看看UIView (WebCacheOperation)这个类别提供的方法:用于操作绑定关系

/**
 *  Set the image load operation (storage in a UIView based dictionary)
 *  设置图像加载操作(存储在和UIView做绑定的字典里面)
 *  @param operation the operation
 *  @param key       key for storing the operation
 */
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key;

/**
 *  Cancel all operations for the current UIView and key
 *  用这个key找到当前UIView上面的所有操作移除并取消
 *  @param key key for identifying the operations
 */
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key;

/**
 *  Just remove the operations corresponding to the current UIView and key without cancelling them
 *  用这个key找到当前UIView上面的所有操作仅仅移除,不取消。
 *  @param key key for identifying the operations
 */
- (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key;

objc_setAssociatedObject 作用是对已存在的类在扩展中添加自定义的属性,通常推荐的做法是添加属性的key最好是static char类型的,通常来说该属性的key应该是常量唯一的。

objc_getAssociatedObject 作用根据key获得与对象绑定的属性。

 - (SDOperationsDictionary *)operationDictionary {
    //objc_getAssociatedObject根据key获得与对象绑定的属性。
    /*
     这个loadOperationKey 的定义是:static char loadOperationKey;
     它对应的绑定在UIView的属性是operationDictionary(NSMutableDictionary类型)
     operationDictionary的value是操作,key是针对不同类型视图和不同类型的操作设定的字符串
     注意:&是一元运算符结果是右操作对象的地址(&loadOperationKey返回static char loadOperationKey的地址)
     */
    SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
    //如果可以查到operations,就返回,反之给视图绑定一个新的,空的operations字典
    if (operations) {
        return operations;
    }
    operations = [NSMutableDictionary dictionary];
    //objc_setAssociatedObject作用是对已存在的类在扩展中添加自定义的属性,通常推荐的做法是添加属性的key最好是static char类型的,通常来说该属性的key应该是常量唯一的。
    objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return operations;
}

    // 根据 key 值 取消正在下载的队列
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    // Cancel in progress downloader from queue
    SDOperationsDictionary *operationDictionary = [self operationDictionary];
    /**
     如果 operationDictionary可以取到,根据key可以得到与视图相关的操作,取消他们
     并根据key值,从operationDictionary里面删除这些操作
*/
    id operations = operationDictionary[key];
    if (operations) {
        if ([operations isKindOfClass:[NSArray class]]) {
            for (id <SDWebImageOperation> operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
            [(id<SDWebImageOperation>) operations cancel];
        }
        [operationDictionary removeObjectForKey:key];
    }
}

接下来我们继续探索- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock

直接简写成sd_internalSetImageWithURL

 - (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 {
  //如果没有给定operationKey,那么获取类的名字,以它为 key
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    //移除UIView等拓展类(这里主要是UIImageView 和 UIButton)当前绑定的操作.当TableView的cell包含的UIImageView被重用的时候首先执行这一行代码,保证这个ImageView的下载和缓存组合操作都被取消
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //若options参数不是SDWebImageDelayPlaceholder,即options不等于SDWebImageDelayPlaceholder,延迟加载占位图一直到图像完成加载意思也就是说图像显示占位图,就执行以下操作
    if (!(options & SDWebImageDelayPlaceholder)) {
        //dispatch_main_async_safe是一个宏定义,因为图像的绘制只能在主线程完成,所以dispatch_main_sync_safe就是为了保证block在主线程中执行
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }

注意: dispatch_main_async_safe 这是一个宏定义。之所以提示一下是因为版本3.8和4.1.2版本代码不一样,作用一样

V4.1.2
获取当前进程的 name 和主线程的相比较,如果相等,则strcmp == 0;即在主线程进行回调 block


#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
    if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }
#endif

V3.8

#define dispatch_main_async_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }

V4.1.2 采用的 GCD 的方式,效率稍高。


  • 接下来 ,继续sd_internalSetImageWithURL
  if (url) {
        // check if activityView is enabled or not
        // 检查是否通过`setShowActivityIndicatorView:`方法设置了显示正在加载指示器。如果设置了,使用`addActivityIndicator`方法向self添加指示器
        if ([self sd_showActivityIndicatorView]) {
            [self sd_addActivityIndicator];
        }
#pragma mark - 下载的核心方法 -
        __weak __typeof(self)wself = self;
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            __strong __typeof (wself) sself = wself;
            //完成下载,移除加载指示器
            [sself sd_removeActivityIndicator];
            //如果这个控件不存在了,就 返回,即暂停
            if (!sself) {
                return;
            }

            dispatch_main_async_safe(^{
                if (!sself) {
                    return;
                }
                /**
                 SDWebImageAvoidAutoSetImage,默认情况下图片会在下载完毕后自动添加给该控件self,
                但是有些时候我们想在设置图片之前加一些图片的处理,就要下载成功后去手动设置图片了,不会执行`wself.image = image;`,而是直接执行完成回调,由用户自己决定如何处理。
                 */

                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                    completedBlock(image, error, cacheType, url);
                    return;
                } else if (image) {
                    //如果后两个条件中至少有一个不满足,那么就直接将image赋给当前的控件,并调用setNeedsLayout
                    [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    [sself sd_setNeedsLayout];
                } else {
                    //image为空,并且设置了延迟设置占位图,会将占位图设置为最终的image,并将其标记为需要重新布局。

                    if ((options & SDWebImageDelayPlaceholder)) {
                        [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                        [sself sd_setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        //为self 控件绑定新的操作,把之前该控件的操作cancel了
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else {
//        判断url不存在,移除加载指示器,执行完成回调,传递错误信息。
        dispatch_main_async_safe(^{
            [self sd_removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

其中下载方法- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;
位于SDWebImageManager 类中,V3.8 中方法名为downloadImageWithURL:.....

SDWebImageManager:这个 SDWebImage 进行操作的核心类。对图片的下载和缓存管理

- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
    // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
    // 防止开发者把传入NSString类型的url,如果url的类型是NSString就给转换成NSURL类型
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }
   // 如果转换NSURL失败,就把传入的url置为nil下载停止
    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

__block用于指明当前声明的变量在被block捕获之后,可以在block中改变变量的值。因为在block声明的同时会截获该block所使用的全部自动变量的值,这些值只在block中只有”使用权”而不具有”修改权”。而block说明符就为block提供了变量的修改权,block不能避免循环引,这就需要我们在 block 内部将要退出的时候手动释放掉 blockObj,blockObj = nil

__weak是所有权修饰符, __weak本身是可以避免循环引用的问题的,但是其会导致外部对象释放之后,block内部也访问不到对象的问题,我们可以通过在block内部声明一个__strong的变量来指向weakObj,使外部既能在block内部保持住又能避免循环引用


SDWebImageCombineOperation

  • SDWebImageCombineOperation : 什么也不做,保存了、(一个block,可以取消下载operation;一个operation,cacheOperation用来下载图片并且缓存的operation;保存了一个状态isCancelled)
 @interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>

@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;

@end 

SDWebImageCombineOperation 的实现

@implementation SDWebImageCombinedOperation

- (void)setCancelBlock:(nullable SDWebImageNoParamsBlock)cancelBlock {
    //检查cancelBlock 的 operation 是否已经取消,如果没有取消,则调用cancleblock
    // check if the operation is already cancelled, then we just call the cancelBlock
    if (self.isCancelled) {
        if (cancelBlock) {
            cancelBlock();
        }
        //记得 cancelblock 为 nil 否则 crash
        _cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
    } else {
        _cancelBlock = [cancelBlock copy];
    }
}

- (void)cancel {
    self.cancelled = YES;
    if (self.cacheOperation) {
        [self.cacheOperation cancel];
        self.cacheOperation = nil;
    }
    if (self.cancelBlock) {
        self.cancelBlock();

        // TODO: this is a temporary fix to #809.
        // Until we can figure the exact cause of the crash, going with the ivar instead of the setter
//        self.cancelBlock = nil;
        _cancelBlock = nil;
    }
}

回到==SDWebImageManager==

 /**    
    self.failedURLs是一个NSSet类型的集合,里面存放的都是下载失败的图片的url,failedURLs不是NSArray类型的原因是:在搜索一个个元素的时候NSSet比NSArray效率高,主要是它用到了一个算法hash(散列,哈希) ,比如你要存储A,一个hash算法直接就能找到A应该存储的位置;同样当你要访问A的时候,一个hash过程就能找到A存储的位置,对于NSArray,若想知道A到底在不在数组中,则需要遍历整个数据,显然效率较低了
   */
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    BOOL isFailedUrl = NO;
    if (url) {
        // @synchronized是OC中一种方便地创建互斥锁的方式--它可以防止不同线程在同一时间执行区块的代码--创建一个互斥锁防止现有的别的线程修改failedURLs
        // 判断这个url是否是fail过的,如果url failed过的,containsObject : 返回 true,即isFailedUrl就是true.
        @synchronized (self.failedURLs) {
            isFailedUrl = [self.failedURLs containsObject:url];
        }
    }
    /**如果url不存在那么直接返回一个block;
    如果url存在那么继续 判断 !(options & SDWebImageRetryFailed)
     它的意思看这个options是不是和SDWebImageRetryFailed不相同,如果不相同并且isFailedUrl是true(这个 url 是 存在请求失败的 URL 集合里面).那么就回调一个error的block,直接返回 operation
    */
    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;
    }

 ```
 如果上面的条件不满足,即url的字符串长度>0 

 ```
  //把operation加入到self.runningOperations的数组里面,并创建一个互斥线程锁来保护这个操作
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }

接下来进行一系列的操作。

  // 获取image的url对应的key
    NSString *key = [self cacheKeyForURL:url];
//是SDImageCache的一个方法,根据图片的key,异步查询磁盘缓存的方法
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        if (operation.isCancelled) {
            // 安全移除 operation 这比 v3版本 要严谨。
            [self safelyRemoveOperationFromRunning:operation];
            return;
        }
        //条件1(!cachedImage || options & SDWebImageRefreshCached):在缓存中没有找到图片或者options == SDWebImageRefreshCached(这两项都需要进行请求网络图片的)
        //条件2:代理允许下载执行imageManager:shouldDownloadImageForURL:方法,SDWebImageManagerDelegate的delegate不能响应imageManager:shouldDownloadImageForURL:方法;;或者能响应方法且方法返回值为YES.也就是没有实现这个方法就是允许的,如果实现了的话,返回YES才是允许

        if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            /**
             如果在缓存中找到了image且options选项包含SDWebImageRefreshCached,
             先在主线程完成一次回调,使用的是缓存中找的图片
             */
            if (cachedImage && options & SDWebImageRefreshCached) {
                // 如果在缓存中找到了image但是设置了SDWebImageRefreshCached选项,传递缓存的image,同时尝试重新下载它来让NSURLCache有机会接收服务器端的更新
                // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
                // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
                [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }
            // 如果没有在缓存中找到image 或者设置了需要请求服务器刷新的选项,则仍需要下载

            // download if no image or requested to refresh anyway, and download allowed by delegate
            SDWebImageDownloaderOptions downloaderOptions = 0;
            // 开始各种 options 的判断。
            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;
            // 如果 image 已经被缓存但是需要设置options = SDWebImageRefreshCached(请求服务器刷新),强制关闭渐进式选项
            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
                //如果image已经被缓存但是设置了需要请求服务器刷新的选项,忽略从NSURLCache读取的image
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }

            //创建下载操作,先使用self.imageDownloader下载
            SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                //如果strongOperation == nil  或者strongOperation操作取消了,不做任何事情
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (!strongOperation || strongOperation.isCancelled) {
                    // Do nothing if the operation was cancelled
                    // See #699 for more details
                    // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
                } else if (error) {
                    //进行完成回调
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
                    //将url添加到失败列表里面
                    if (   error.code != NSURLErrorNotConnectedToInternet
                        && error.code != NSURLErrorCancelled
                        && error.code != NSURLErrorTimedOut
                        && error.code != NSURLErrorInternationalRoamingOff
                        && error.code != NSURLErrorDataNotAllowed
                        && error.code != NSURLErrorCannotFindHost
                        && error.code != NSURLErrorCannotConnectToHost
                        && error.code != NSURLErrorNetworkConnectionLost) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                     //如果设置了下载失败重试,将url从失败列表中去掉,if 条件options == SDWebImageRetryFailed
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    //options包含了SDWebImageRefreshCached选项,且缓存中找到了image且没有下载成功
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                        // 图片刷新遇到了NSSURLCache中有缓存的状况,不调用 回调
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                    } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        ///图片下载成功 并且 设置了需要变形Image(gif)的选项且变形的代理方法已经实现
                        ////全局队列异步执行
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            ////调用代理方法完成图片transform
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                // pass nil if the image was transformed, so we can recalculate the data from the image
                                // 对已经transform的图片进行缓存
                                [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
                            }
                            //主线程执行完成回调
                            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        });
                    } else {
                        // 如果没有图片transform的需求,且图片下载完成且图片存在就直接缓存
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                        }
                        //主线程完成回调
                        [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                    }
                }
                // finished 完成,从正在进行的操作列表中移除这组合操作
                if (finished) {
                    [self safelyRemoveOperationFromRunning:strongOperation];
                }
            }];
            //设置组合操作取消得得回调
            operation.cancelBlock = ^{
                [self.imageDownloader cancel:subOperationToken];
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                [self safelyRemoveOperationFromRunning:strongOperation];
            };
        } else if (cachedImage) {

            //在缓存中找到图片(代理不允许下载 或者没有设置SDWebImageRefreshCached选项  满足至少一项)
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation 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
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        }
    }];

    return operation;
}

这里用到的方法callCompletionBlockForOperation:... 只不过是对下面的封装。实现:

 dispatch_main_async_safe(^{
        if (operation && !operation.isCancelled && completionBlock) {
            completionBlock(image, data, error, cacheType, finished, url);
        }
    });

但是版本 V3.8.x里面是这样实现的都用的同步的方法,没有对其封装 ,如下

   dispatch_main_sync_safe(^{
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
            completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
        });

两者都确保更新 ui 在主线程更新, V3.8.x 是同步,V4.1.x是异步


这里有用到SDImageCache、SDWebImageDownloader


SDImageCache
在项目中使用的单例,其真正的初始化方法:

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory {
    if ((self = [super init])) {
        // 这是 cache 缓存的 地方,一个文件夹。
        NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
        //创建一个自定义的串行队列。DISPATCH_QUEUE_SERIAL代表的是创建一个串行的队列,所以_ioQueue是一个串行(DISPATCH_QUEUE_SERIAL)队列(任务一个执行完毕才执行下一个)
        // Create IO serial queue
        _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);

        _config = [[SDImageCacheConfig alloc] init];

        // Init the memory cache
        _memCache = [[AutoPurgeCache alloc] init];
        _memCache.name = fullNamespace;

        // Init the disk cache
        这里directory :沙盒 /Library/Caches/default
        if (directory != nil) {
        //  ../Library/Caches/default/com.hackemist.SDWebImageCache.default
            _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
        } else {
            NSString *path = [self makeDiskCachePath:ns];
            _diskCachePath = path;
        }

        dispatch_sync(_ioQueue, ^{
            _fileManager = [NSFileManager new];
        });

#if SD_UIKIT
        // Subscribe to app events
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(clearMemory)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(deleteOldFiles)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundDeleteOldFiles)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
#endif
    }

    return self;
}


- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
// 路径是沙盒下,/Library/Caches 的地址 ,在 default
    NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    return [paths[0] stringByAppendingPathComponent:fullNamespace];
}

存储的方法

- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
    if (!imageData || !key) {
        return;
    }
    // 当前队列是不是初始化的  IO队列
    [self checkIfQueueIsIOQueue];

    // 如果缓存的文件夹不存在,这创建一个文件夹。
    if (![_fileManager fileExistsAtPath:_diskCachePath]) {
        [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }

    // get cache Path for image key
    // 根据当前图片的 key 值 根据某种算法得出对应的缓存图片 的路径名字
    NSString *cachePathForKey = [self defaultCachePathForKey:key];
    // transform to NSUrl
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    // 对 image进行保存
    [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];

    // disable iCloud backup
    if (self.config.shouldDisableiCloud) {
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

defaultCachePathForKey: 里面调用:

- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
    const char *str = key.UTF8String;
    if (str == NULL) {
        str = "";
    }
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    CC_MD5(str, (CC_LONG)strlen(str), r);
    //16进制格式命名
    NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
                          r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
                          r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];

    return filename;
}

SDWebImageManager里面用到的 SDImageCache中 store 的方法;

key值 是在SDWebImageManager中得到的,如下:(一般是 url.absoluteString)

// 如果检测到cacheKeyFilter不为空的时候,利用cacheKeyFilter来生成一个key
//如果为空,那么直接返回URL的string内容,当做key.
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
    if (!url) {
        return @"";
    }

    if (self.cacheKeyFilter) {
        return self.cacheKeyFilter(url);
    } else {
        return url.absoluteString;
    }
}

猜你喜欢

转载自blog.csdn.net/yanyanforest/article/details/78376796
今日推荐