SDWebImage加載奕剃、緩存的處理

SDWebImage 的github上有它的UML圖:


SDWebImageClassDiagram.png

5.0版本之后做了一些類的重構(gòu)滑频,把里面小的組件抽離出來(lái)作為一個(gè)獨(dú)立的庫(kù)。

我們應(yīng)該學(xué)會(huì)看懂它权均,這里簡(jiǎn)單的記錄一下:

泛化(generalization):

表示is-a的關(guān)系,是對(duì)象之間耦合度最大的一種關(guān)系,子類繼承父類的所有細(xì)節(jié)燥狰。在類圖中使用帶有三角箭頭的實(shí)線表示,箭頭從子類指向父類斜筐。
下面這張圖我們可以看到 SDMemoryCache 就是繼承的系統(tǒng)的 NSCache類:


屏幕快照 2019-09-03 下午9.45.20.png

實(shí)現(xiàn)(realization):

在類圖中就是接口和實(shí)現(xiàn)的關(guān)系龙致,在類圖中使用帶三角箭頭的虛線表示,箭頭從實(shí)現(xiàn)類指向接口顷链。
5.0之后目代,SDWebImage也是主要采用了協(xié)議解耦。
下面這張圖就是SDWebImage中實(shí)現(xiàn)接口的例子:


屏幕快照 2019-09-03 下午9.47.19.png

聚合(aggregation)

表示has-a的關(guān)系,是一種不穩(wěn)定的包含關(guān)系像啼。較強(qiáng)于一般關(guān)聯(lián)俘闯,有整體和局部的關(guān)系,并且沒有了整體忽冻,局部也可單獨(dú)存在真朗。如公司和員工的關(guān)系,公司包含員工僧诚,但公司倒閉遮婶,員工依然可以換公司。在類圖中使用空心的菱形表示湖笨,菱形從局部指向整體旗扑。
下圖就是SDWebImage中的聚合關(guān)系:


屏幕快照 2019-09-03 下午9.50.32.png

組合(composition)

表示contains-a的關(guān)系,是一種強(qiáng)烈的包含關(guān)系慈省。組合類負(fù)責(zé)被組合類的生命周期臀防。是一種更強(qiáng)的聚合關(guān)系。部分不能脫離整體存在边败。比如公司和部門的關(guān)系袱衷,沒有了公司,部門也就不存在了笑窜,調(diào)查問卷中問題和選項(xiàng)的關(guān)系致燥,訂單和訂單選項(xiàng)的關(guān)系。在類圖使用實(shí)心的菱形表示排截,菱形從局部指向整體嫌蚤。
下面是 SDWebImage 4.0的組合關(guān)系的局部圖:


屏幕快照 2019-09-03 下午9.53.00.png

我們可以抽時(shí)間對(duì)SDWebImage有一個(gè)整體的認(rèn)識(shí),項(xiàng)目中我們用的最多的應(yīng)該就是UIImageView+WebCache.m的這個(gè)方法了:

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder;

我們可以看到断傲,這個(gè)方法最終會(huì)調(diào)用到下面這個(gè)方法(還是在當(dāng)前文件):

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                   context:(nullable SDWebImageContext *)context
                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock {
    [self sd_internalSetImageWithURL:url
                    placeholderImage:placeholder
                             options:options
                             context:context
                       setImageBlock:nil
                            progress:progressBlock
                           completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
                               if (completedBlock) {
                                   completedBlock(image, error, cacheType, imageURL);
                               }
                           }];
}

這個(gè)方法最主要的事情就是設(shè)置圖片脱吱,設(shè)置圖片的事情是交由Manager去管理的,現(xiàn)在我們繼續(xù)看這個(gè)方法:

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                           context:(nullable SDWebImageContext *)context
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDImageLoaderProgressBlock)progressBlock
                         completed:(nullable SDInternalCompletionBlock)completedBlock {
    context = [context copy]; // copy to avoid mutable object
    
    // 判斷當(dāng)前控件上是否有其他任務(wù)认罩,有的話取消急凰,比如Cell復(fù)用時(shí),以新的為主
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
    if (!validOperationKey) {
        validOperationKey = NSStringFromClass([self class]);
    }
    self.sd_latestOperationKey = validOperationKey;
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    self.sd_imageURL = url;
    
// 這段代碼的主要作用就是判斷如果當(dāng)前的options不是延遲設(shè)置placeholder猜年,就回到主線程當(dāng)中設(shè)置placeholder抡锈。
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        });
    }
    
    if (url) {
        // 重置下載進(jìn)度
        NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
        if (imageProgress) {
            imageProgress.totalUnitCount = 0;
            imageProgress.completedUnitCount = 0;
        }
        
#if SD_UIKIT || SD_MAC
        // check and start image indicator
        [self sd_startImageIndicator];
        id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
        
        SDWebImageManager *manager = context[SDWebImageContextCustomManager];
        if (!manager) {
            manager = [SDWebImageManager sharedManager];
        }
        
// 這個(gè)就是設(shè)置下載進(jìn)度回調(diào),修改下載進(jìn)度乔外,顯示imageIndicator
        SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
            if (imageProgress) {
                imageProgress.totalUnitCount = expectedSize;
                imageProgress.completedUnitCount = receivedSize;
            }
#if SD_UIKIT || SD_MAC
            if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) {
                double progress = 0;
                if (expectedSize != 0) {
                    progress = (double)receivedSize / expectedSize;
                }
                progress = MAX(MIN(progress, 1), 0); // 0.0 - 1.0
                dispatch_async(dispatch_get_main_queue(), ^{
                    [imageIndicator updateIndicatorProgress:progress];
                });
            }
#endif
            if (progressBlock) {
                progressBlock(receivedSize, expectedSize, targetURL);
            }
        };
        @weakify(self);
      // 最重要的方法
// 加載圖片
        id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            @strongify(self);
            if (!self) { return; }
            // 如果進(jìn)度沒有被更改床三,但是已經(jīng)完成了,就設(shè)置為完成的進(jìn)度
            if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
                imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
                imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
            }
            
#if SD_UIKIT || SD_MAC
            // 停止 image indicator
            if (finished) {
                [self sd_stopImageIndicator];
            }
#endif
            // 這個(gè)就是先通過(guò)options判斷杨幼,是否需要獲取到圖片之后撇簿,自動(dòng)顯示聂渊,如果不需要設(shè)置image,這里會(huì)調(diào)用完 completedBlock 直接就return了四瘫。
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
                if (!self) { return; }
                if (!shouldNotSetImage) {
                    [self sd_setNeedsLayout];
                }
                if (completedBlock && shouldCallCompletedBlock) {
                    completedBlock(image, data, error, cacheType, finished, 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
            if (shouldNotSetImage) {
    // 如果不需要設(shè)置image汉嗽,那么這里直接走回調(diào)的block,然后return了找蜜。               dispatch_main_async_safe(callCompletedBlockClojure);
                return;
            }
            
// 如果允許設(shè)置圖片饼暑,就開始設(shè)置顯示圖片了
            UIImage *targetImage = nil;
            NSData *targetData = nil;
            if (image) {
                // case 2a: 如果options 不是SDWebImageAvoidAutoSetImage
                targetImage = image;
                targetData = data;
            } else if (options & SDWebImageDelayPlaceholder) {
                // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                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 = self.sd_imageTransition;
            }
#endif
            dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
                [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
                [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif
                callCompletedBlockClojure();
            });
        }];
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else {
#if SD_UIKIT || SD_MAC
        [self sd_stopImageIndicator];
#endif
// 完成的回調(diào)
        dispatch_main_async_safe(^{
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
                completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
            }
        });
    }
}

我們現(xiàn)在可以一點(diǎn)一點(diǎn)分析一下:

//判斷當(dāng)前控件上是否有其他任務(wù),有的話取消洗做,比如Cell復(fù)用時(shí)弓叛,以新的為主
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
    if (!validOperationKey) {
        validOperationKey = NSStringFromClass([self class]);
    }
    self.sd_latestOperationKey = validOperationKey;
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
========> sd_cancelImageLoadOperationWithKey方法:
// 這個(gè)方法就是調(diào)用了operation的cancel方法
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    if (key) {
        // Cancel in progress downloader from queue
        SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
        id<SDWebImageOperation> operation;
        
        @synchronized (self) {
            operation = [operationDictionary objectForKey:key];
        }
        if (operation) {
            if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
                [operation cancel];
            }
            @synchronized (self) {
                [operationDictionary removeObjectForKey:key];
            }
        }
    }
}

這個(gè)operation是怎么來(lái)的,又是怎么添加進(jìn) SDOperationsDictionary 的诚纸,我們可以繼續(xù)向下看撰筷,去掉無(wú)關(guān)的代碼,我們可以看到:

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                           context:(nullable SDWebImageContext *)context
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDImageLoaderProgressBlock)progressBlock
                         completed:(nullable SDInternalCompletionBlock)completedBlock {    
...
 // 1.第一步判斷是否有舊的任務(wù)畦徘,有的話就取消
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
    if (!validOperationKey) {
        validOperationKey = NSStringFromClass([self class]);
    }
    self.sd_latestOperationKey = validOperationKey;
    [self  sd_cancelImageLoadOperationWithKey:validOperationKey];
    self.sd_imageURL = url;
    
...
// 2.第二步構(gòu)造一個(gè)新的operation
    id <SDWebImageOperation> operation = ...

// 3.第三部添加進(jìn)去
    [self sd_setImageLoadOperation:operation forKey:validOperationKey];    
...
}

這就是一整個(gè)取消和添加的流程毕籽,這個(gè)operation實(shí)現(xiàn)了 SDWebImageOperation,這個(gè)協(xié)議定義最主要的行為就是cancel操作:

/// A protocol represents cancelable operation.
@protocol SDWebImageOperation <NSObject>

- (void)cancel;

@end

現(xiàn)在又要到關(guān)鍵的一步了井辆,也就是構(gòu)建operation的方法影钉,要真正的去開始一個(gè)任務(wù)了:

- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                          options:(SDWebImageOptions)options
                                          context:(nullable SDWebImageContext *)context
                                         progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                        completed:(nonnull SDInternalCompletionBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    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.
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

// 用來(lái)耦合緩存任務(wù)和下載任務(wù)
    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    operation.manager = self;

    BOOL isFailedUrl = NO;
    if (url) {
        SD_LOCK(self.failedURLsLock);
        isFailedUrl = [self.failedURLs containsObject:url];
        SD_UNLOCK(self.failedURLsLock);
    }

    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}] url:url];
        return operation;
    }

    SD_LOCK(self.runningOperationsLock);
    [self.runningOperations addObject:operation];
    SD_UNLOCK(self.runningOperationsLock);
    
    // Preprocess the options and context arg to decide the final the result for manager
    SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
    
    // 開始從緩存中加載圖片,優(yōu)先加載緩存掘剪,它會(huì)創(chuàng)建一個(gè)緩存查找任務(wù),然后給我們當(dāng)前創(chuàng)建的 SDWebImageCombinedOperation 賦值
    [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];

    return operation;
}

上面的方法的一個(gè)補(bǔ)充:

// 用來(lái)耦合緩存任務(wù)和下載任務(wù)
    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    operation.manager = self;
======> SDWebImageCombinedOperation的具體實(shí)現(xiàn):
// SDWebImageCombinedOperation 也實(shí)現(xiàn)了 < SDWebImageOperation >協(xié)議
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
// 取消
- (void)cancel;
// 緩存任務(wù)
@property (strong, nonatomic, nullable, readonly) id<SDWebImageOperation> cacheOperation;
// 下載任務(wù)
@property (strong, nonatomic, nullable, readonly) id<SDWebImageOperation> loaderOperation;
@end
// 當(dāng)取消任務(wù)的時(shí)候奈虾,實(shí)際上也是同時(shí)取消了緩存任務(wù)和下載任務(wù):
======> SDWebImageCombinedOperation  cancel方法:
- (void)cancel {
    @synchronized(self) {
        if (self.isCancelled) {
            return;
        }
        self.cancelled = YES;
        if (self.cacheOperation) {
            [self.cacheOperation cancel];
            self.cacheOperation = nil;
        }
        if (self.loaderOperation) {
            [self.loaderOperation cancel];
            self.loaderOperation = nil;
        }
        [self.manager safelyRemoveOperationFromRunning:self];
    }
}

我們現(xiàn)在要去看如何創(chuàng)建緩存查找任務(wù):

// Query cache process
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                 url:(nonnull NSURL *)url
                             options:(SDWebImageOptions)options
                             context:(nullable SDWebImageContext *)context
                            progress:(nullable SDImageLoaderProgressBlock)progressBlock
                           completed:(nullable SDInternalCompletionBlock)completedBlock {
    // Check whether we should query cache
    BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
    if (shouldQueryCache) {
        id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
        NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
        @weakify(operation);
// 創(chuàng)建一個(gè)緩存查找任務(wù)夺谁,賦值給上一個(gè)方法中的SDWebImageCombinedOperation 的實(shí)例
        operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
            @strongify(operation);
            if (!operation || operation.isCancelled) {
                // Image combined operation cancelled by user
                [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil] url:url];
                [self safelyRemoveOperationFromRunning:operation];
                return;
            }
            // Continue download process
            [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
        }];
    } else {
        // Continue download process
        [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
    }
}

我們?cè)谏厦孢@個(gè)方法中可以看到,使用了 self.imageCache 構(gòu)建了一個(gè)緩存operation肉微,那么這個(gè)緩存是如何實(shí)現(xiàn)的呢匾鸥?

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
 
    // 1.首先查找內(nèi)存緩存中當(dāng)前的image
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    
    if (image) {
        if (options & SDImageCacheDecodeFirstFrameOnly) {
            // Ensure static image
            Class animatedImageClass = image.class;
            if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) {
#if SD_MAC
                image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
#else
                image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
#endif
            }
        } else if (options & SDImageCacheMatchAnimatedImageClass) {
            // Check image class matching
            Class animatedImageClass = image.class;
            Class desiredImageClass = context[SDWebImageContextAnimatedImageClass];
            if (desiredImageClass && ![animatedImageClass isSubclassOfClass:desiredImageClass]) {
                image = nil;
            }
        }
    }

    BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryMemoryData));
    if (shouldQueryMemoryOnly) {
        if (doneBlock) {
            doneBlock(image, nil, SDImageCacheTypeMemory);
        }
        return nil;
    }
    
    // Second check the disk cache...
    NSOperation *operation = [NSOperation new];
    // Check whether we need to synchronously query disk
    // 1. in-memory cache hit & memoryDataSync
    // 2. in-memory cache miss & diskDataSync
    BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
                                (!image && options & SDImageCacheQueryDiskDataSync));
    void(^queryDiskBlock)(void) =  ^{
        if (operation.isCancelled) {
            if (doneBlock) {
                doneBlock(nil, nil, SDImageCacheTypeNone);
            }
            return;
        }
        
        @autoreleasepool {
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage;
            SDImageCacheType cacheType = SDImageCacheTypeNone;
            if (image) {
                // the image is from in-memory cache, but need image data
                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 context:context];
                if (diskImage && self.config.shouldCacheImagesInMemory) {
                    NSUInteger cost = diskImage.sd_memoryCost;
                    [self.memoryCache setObject:diskImage forKey:key cost:cost];
                }
            }
            
            if (doneBlock) {
                if (shouldQueryDiskSync) {
                    doneBlock(diskImage, diskData, cacheType);
                } else {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        doneBlock(diskImage, diskData, cacheType);
                    });
                }
            }
        }
    };
    
    // Query in ioQueue to keep IO-safe
    if (shouldQueryDiskSync) {
        dispatch_sync(self.ioQueue, queryDiskBlock);
    } else {
        dispatch_async(self.ioQueue, queryDiskBlock);
    }
    
    return operation;
}

我們刪除無(wú)用的代碼,可以看到第一步是先從內(nèi)存中查找圖片:

// 首先查找內(nèi)存緩存中當(dāng)前的image
    UIImage *image = [self imageFromMemoryCacheForKey:key];
=====> imageFromMemoryCacheForKey 方法實(shí)現(xiàn):
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
    return [self.memoryCache objectForKey:key];
}
我們可以看到 memoryCache 其實(shí)就是:
@property (nonatomic, strong, readonly, nonnull) id<SDMemoryCache> memoryCache;
它實(shí)際上就是一個(gè)遵循了SDMemoryCache 協(xié)議的 SDMemoryCache對(duì)象(在代碼中可以看到):
======> SDMemoryCache 協(xié)議:
這個(gè)協(xié)議主要是定義了一個(gè)緩存必須的存入查找的方法列表
@protocol SDMemoryCache <NSObject>
- (nonnull instancetype)initWithConfig:(nonnull SDImageCacheConfig *)config;
- (nullable id)objectForKey:(nonnull id)key;
- (void)setObject:(nullable id)object forKey:(nonnull id)key;
- (void)setObject:(nullable id)object forKey:(nonnull id)key cost:(NSUInteger)cost;
- (void)removeObjectForKey:(nonnull id)key;
- (void)removeAllObjects;
@end
======> SDMemoryCache類:繼承自系統(tǒng)的NSCache
@interface SDMemoryCache <KeyType, ObjectType> : NSCache <KeyType, ObjectType> <SDMemoryCache>
@property (nonatomic, strong, nonnull, readonly) SDImageCacheConfig *config;
@end

這里定義一個(gè) SDMemoryCache協(xié)議主要是為了解耦碉纳,不想因?yàn)槔^承NSCache造成的耦合勿负。
SDWebImage 5.0之后,主要是使用協(xié)議進(jìn)行解耦劳曹。

NSCache另寫了一篇奴愉,來(lái)進(jìn)行補(bǔ)充。

接下來(lái)看下SDWebImage是如何處理NSURLCache的铁孵,可以先看下NSURLCache的相關(guān)文章锭硼。

SDWebImage避免NSURLCache的相關(guān)操作:
SDWebImageDownloader.m 文件中

// 如果用戶不設(shè)置使用NSURLCache,就默認(rèn)忽略本地緩存蜕劝,防止重復(fù)緩存
    //
    NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
    mutableRequest.HTTPShouldHandleCookies = SD_OPTIONS_CONTAINS(options, SDWebImageDownloaderHandleCookies);
    mutableRequest.HTTPShouldUsePipelining = YES;
    SD_LOCK(self.HTTPHeadersLock);
    mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
    SD_UNLOCK(self.HTTPHeadersLock);
    id<SDWebImageDownloaderRequestModifier> requestModifier;
    if ([context valueForKey:SDWebImageContextDownloadRequestModifier]) {
        requestModifier = [context valueForKey:SDWebImageContextDownloadRequestModifier];
    } else {
        requestModifier = self.requestModifier;
    }

SDWebImageDownloaderOperation.m 文件中:

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
    
    NSCachedURLResponse *cachedResponse = proposedResponse;

    // 這個(gè)就是避免本地緩存
    if (!(self.options & SDWebImageDownloaderUseNSURLCache)) {
        // Prevents caching of responses
        cachedResponse = nil;
    }
    if (completionHandler) {
        completionHandler(cachedResponse);
    }
}

如果緩存策略為SDWebImageDownloaderIgnoreCachedResponse忽略本地緩存檀头,SDWebImage會(huì)對(duì)比本地緩存和服務(wù)器數(shù)據(jù)是否一樣轰异,一樣的話,則會(huì)返回304的錯(cuò)誤暑始,這里并不是真正的錯(cuò)誤搭独,只是為了方便之后的處理。


屏幕快照 2019-09-22 下午6.50.26.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末廊镜,一起剝皮案震驚了整個(gè)濱河市牙肝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌期升,老刑警劉巖惊奇,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異播赁,居然都是意外死亡颂郎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門容为,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)乓序,“玉大人,你說(shuō)我怎么就攤上這事坎背√媾” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵得滤,是天一觀的道長(zhǎng)陨献。 經(jīng)常有香客問我,道長(zhǎng)懂更,這世上最難降的妖魔是什么眨业? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮沮协,結(jié)果婚禮上龄捡,老公的妹妹穿的比我還像新娘。我一直安慰自己慷暂,他們只是感情好聘殖,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著行瑞,像睡著了一般奸腺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上血久,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天洋机,我揣著相機(jī)與錄音,去河邊找鬼洋魂。 笑死绷旗,一個(gè)胖子當(dāng)著我的面吹牛喜鼓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播衔肢,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼庄岖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了角骤?” 一聲冷哼從身側(cè)響起隅忿,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎邦尊,沒想到半個(gè)月后背桐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝉揍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年链峭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片又沾。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弊仪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出杖刷,到底是詐尸還是另有隱情励饵,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布滑燃,位于F島的核電站役听,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏表窘。R本人自食惡果不足惜典予,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蚊丐。 院中可真熱鬧,春花似錦艳吠、人聲如沸麦备。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)凛篙。三九已至,卻和暖如春栏渺,著一層夾襖步出監(jiān)牢的瞬間呛梆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工磕诊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留填物,地道東北人纹腌。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像滞磺,于是被迫代替她去往敵國(guó)和親升薯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,092評(píng)論 1 32
  • 圖片下載的這些回調(diào)信息存儲(chǔ)在SDWebImageDownloader類的URLOperations屬性中击困,該屬性是...
    怎樣m閱讀 2,365評(píng)論 0 1
  • 下載 下載管理器 SDWebImageDownLoader作為一個(gè)單例來(lái)管理圖片的下載操作涎劈。圖片的下載是放在一個(gè)N...
    wind_dy閱讀 1,462評(píng)論 0 1
  • 基本結(jié)構(gòu) 閑言少敘,咱們這就開始阅茶。 首先咱們來(lái)看看 SDWebImage 的整體結(jié)構(gòu): 有一個(gè)專門的 Cache ...
    執(zhí)著的形狀0526閱讀 924評(píng)論 0 0
  • 1.首先使用SDWebImage才能進(jìn)行剖析(以UIImageView+WebCache.h為例) 附:由于有些操...
    一川煙草i蓑衣閱讀 516評(píng)論 0 0