SDWebImage 的github上有它的UML圖:
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類:
實(shí)現(xiàn)(realization):
在類圖中就是接口和實(shí)現(xiàn)的關(guān)系龙致,在類圖中使用帶三角箭頭的虛線表示,箭頭從實(shí)現(xiàn)類指向接口顷链。
5.0之后目代,SDWebImage也是主要采用了協(xié)議解耦。
下面這張圖就是SDWebImage中實(shí)現(xiàn)接口的例子:
聚合(aggregation)
表示has-a的關(guān)系,是一種不穩(wěn)定的包含關(guān)系像啼。較強(qiáng)于一般關(guān)聯(lián)俘闯,有整體和局部的關(guān)系,并且沒有了整體忽冻,局部也可單獨(dú)存在真朗。如公司和員工的關(guān)系,公司包含員工僧诚,但公司倒閉遮婶,員工依然可以換公司。在類圖中使用空心的菱形表示湖笨,菱形從局部指向整體旗扑。
下圖就是SDWebImage中的聚合關(guān)系:
組合(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)系的局部圖:
我們可以抽時(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ò)誤搭独,只是為了方便之后的處理。