|
@@ -13,6 +13,7 @@
|
|
|
#import "SDAssociatedObject.h"
|
|
|
#import "SDWebImageError.h"
|
|
|
#import "SDInternalMacros.h"
|
|
|
+#import "SDCallbackQueue.h"
|
|
|
|
|
|
static id<SDImageCache> _defaultImageCache;
|
|
|
static id<SDImageLoader> _defaultImageLoader;
|
|
@@ -26,14 +27,15 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
|
|
|
@end
|
|
|
|
|
|
-@interface SDWebImageManager ()
|
|
|
+@interface SDWebImageManager () {
|
|
|
+ SD_LOCK_DECLARE(_failedURLsLock); // a lock to keep the access to `failedURLs` thread-safe
|
|
|
+ SD_LOCK_DECLARE(_runningOperationsLock); // a lock to keep the access to `runningOperations` thread-safe
|
|
|
+}
|
|
|
|
|
|
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
|
|
|
@property (strong, nonatomic, readwrite, nonnull) id<SDImageLoader> imageLoader;
|
|
|
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
|
|
|
-@property (strong, nonatomic, nonnull) dispatch_semaphore_t failedURLsLock; // a lock to keep the access to `failedURLs` thread-safe
|
|
|
@property (strong, nonatomic, nonnull) NSMutableSet<SDWebImageCombinedOperation *> *runningOperations;
|
|
|
-@property (strong, nonatomic, nonnull) dispatch_semaphore_t runningOperationsLock; // a lock to keep the access to `runningOperations` thread-safe
|
|
|
|
|
|
@end
|
|
|
|
|
@@ -87,9 +89,9 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
_imageCache = cache;
|
|
|
_imageLoader = loader;
|
|
|
_failedURLs = [NSMutableSet new];
|
|
|
- _failedURLsLock = dispatch_semaphore_create(1);
|
|
|
+ SD_LOCK_INIT(_failedURLsLock);
|
|
|
_runningOperations = [NSMutableSet new];
|
|
|
- _runningOperationsLock = dispatch_semaphore_create(1);
|
|
|
+ SD_LOCK_INIT(_runningOperationsLock);
|
|
|
}
|
|
|
return self;
|
|
|
}
|
|
@@ -111,6 +113,26 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
return key;
|
|
|
}
|
|
|
|
|
|
+- (nullable NSString *)originalCacheKeyForURL:(nullable NSURL *)url context:(nullable SDWebImageContext *)context {
|
|
|
+ if (!url) {
|
|
|
+ return @"";
|
|
|
+ }
|
|
|
+
|
|
|
+ NSString *key;
|
|
|
+ // Cache Key Filter
|
|
|
+ id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;
|
|
|
+ if (context[SDWebImageContextCacheKeyFilter]) {
|
|
|
+ cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
|
|
|
+ }
|
|
|
+ if (cacheKeyFilter) {
|
|
|
+ key = [cacheKeyFilter cacheKeyForURL:url];
|
|
|
+ } else {
|
|
|
+ key = url.absoluteString;
|
|
|
+ }
|
|
|
+
|
|
|
+ return key;
|
|
|
+}
|
|
|
+
|
|
|
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url context:(nullable SDWebImageContext *)context {
|
|
|
if (!url) {
|
|
|
return @"";
|
|
@@ -149,7 +171,7 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
id<SDImageTransformer> transformer = self.transformer;
|
|
|
if (context[SDWebImageContextImageTransformer]) {
|
|
|
transformer = context[SDWebImageContextImageTransformer];
|
|
|
- if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
|
|
|
+ if ([transformer isEqual:NSNull.null]) {
|
|
|
transformer = nil;
|
|
|
}
|
|
|
}
|
|
@@ -188,43 +210,55 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
|
|
|
BOOL isFailedUrl = NO;
|
|
|
if (url) {
|
|
|
- SD_LOCK(self.failedURLsLock);
|
|
|
+ SD_LOCK(_failedURLsLock);
|
|
|
isFailedUrl = [self.failedURLs containsObject:url];
|
|
|
- SD_UNLOCK(self.failedURLsLock);
|
|
|
+ SD_UNLOCK(_failedURLsLock);
|
|
|
}
|
|
|
+
|
|
|
+ // Preprocess the options and context arg to decide the final the result for manager
|
|
|
+ SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
|
|
|
|
|
|
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
|
|
|
NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil";
|
|
|
NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL;
|
|
|
- [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] url:url];
|
|
|
+ [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] queue:result.context[SDWebImageContextCallbackQueue] url:url];
|
|
|
return operation;
|
|
|
}
|
|
|
|
|
|
- SD_LOCK(self.runningOperationsLock);
|
|
|
+ SD_LOCK(_runningOperationsLock);
|
|
|
[self.runningOperations addObject:operation];
|
|
|
- SD_UNLOCK(self.runningOperationsLock);
|
|
|
+ SD_UNLOCK(_runningOperationsLock);
|
|
|
|
|
|
- // Preprocess the options and context arg to decide the final the result for manager
|
|
|
- SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
|
|
|
+ // Start the entry to load image from cache, the longest steps are below
|
|
|
+ // Steps without transformer:
|
|
|
+ // 1. query image from cache, miss
|
|
|
+ // 2. download data and image
|
|
|
+ // 3. store image to cache
|
|
|
|
|
|
- // Start the entry to load image from cache
|
|
|
+ // Steps with transformer:
|
|
|
+ // 1. query transformed image from cache, miss
|
|
|
+ // 2. query original image from cache, miss
|
|
|
+ // 3. download data and image
|
|
|
+ // 4. do transform in CPU
|
|
|
+ // 5. store original image to cache
|
|
|
+ // 6. store transformed image to cache
|
|
|
[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
|
|
|
|
|
|
return operation;
|
|
|
}
|
|
|
|
|
|
- (void)cancelAll {
|
|
|
- SD_LOCK(self.runningOperationsLock);
|
|
|
+ SD_LOCK(_runningOperationsLock);
|
|
|
NSSet<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy];
|
|
|
- SD_UNLOCK(self.runningOperationsLock);
|
|
|
+ SD_UNLOCK(_runningOperationsLock);
|
|
|
[copiedOperations makeObjectsPerformSelector:@selector(cancel)]; // This will call `safelyRemoveOperationFromRunning:` and remove from the array
|
|
|
}
|
|
|
|
|
|
- (BOOL)isRunning {
|
|
|
BOOL isRunning = NO;
|
|
|
- SD_LOCK(self.runningOperationsLock);
|
|
|
+ SD_LOCK(_runningOperationsLock);
|
|
|
isRunning = (self.runningOperations.count > 0);
|
|
|
- SD_UNLOCK(self.runningOperationsLock);
|
|
|
+ SD_UNLOCK(_runningOperationsLock);
|
|
|
return isRunning;
|
|
|
}
|
|
|
|
|
@@ -232,15 +266,15 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
if (!url) {
|
|
|
return;
|
|
|
}
|
|
|
- SD_LOCK(self.failedURLsLock);
|
|
|
+ SD_LOCK(_failedURLsLock);
|
|
|
[self.failedURLs removeObject:url];
|
|
|
- SD_UNLOCK(self.failedURLsLock);
|
|
|
+ SD_UNLOCK(_failedURLsLock);
|
|
|
}
|
|
|
|
|
|
- (void)removeAllFailedURLs {
|
|
|
- SD_LOCK(self.failedURLsLock);
|
|
|
+ SD_LOCK(_failedURLsLock);
|
|
|
[self.failedURLs removeAllObjects];
|
|
|
- SD_UNLOCK(self.failedURLsLock);
|
|
|
+ SD_UNLOCK(_failedURLsLock);
|
|
|
}
|
|
|
|
|
|
#pragma mark - Private
|
|
@@ -253,13 +287,10 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
progress:(nullable SDImageLoaderProgressBlock)progressBlock
|
|
|
completed:(nullable SDInternalCompletionBlock)completedBlock {
|
|
|
// Grab the image cache to use
|
|
|
- id<SDImageCache> imageCache;
|
|
|
- if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
|
|
|
- imageCache = context[SDWebImageContextImageCache];
|
|
|
- } else {
|
|
|
+ id<SDImageCache> imageCache = context[SDWebImageContextImageCache];
|
|
|
+ if (!imageCache) {
|
|
|
imageCache = self.imageCache;
|
|
|
}
|
|
|
-
|
|
|
// Get the query cache type
|
|
|
SDImageCacheType queryCacheType = SDImageCacheTypeAll;
|
|
|
if (context[SDWebImageContextQueryCacheType]) {
|
|
@@ -269,21 +300,26 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
// Check whether we should query cache
|
|
|
BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
|
|
|
if (shouldQueryCache) {
|
|
|
+ // transformed cache key
|
|
|
NSString *key = [self cacheKeyForURL:url context:context];
|
|
|
@weakify(operation);
|
|
|
operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType 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:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url];
|
|
|
+ [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] queue:context[SDWebImageContextCallbackQueue] url:url];
|
|
|
[self safelyRemoveOperationFromRunning:operation];
|
|
|
return;
|
|
|
- } else if (context[SDWebImageContextImageTransformer] && !cachedImage) {
|
|
|
- // Have a chance to query original cache instead of downloading
|
|
|
- [self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
|
|
|
- return;
|
|
|
+ } else if (!cachedImage) {
|
|
|
+ NSString *originKey = [self originalCacheKeyForURL:url context:context];
|
|
|
+ BOOL mayInOriginalCache = ![key isEqualToString:originKey];
|
|
|
+ // Have a chance to query original cache instead of downloading, then applying transform
|
|
|
+ // Thumbnail decoding is done inside SDImageCache's decoding part, which does not need post processing for transform
|
|
|
+ if (mayInOriginalCache) {
|
|
|
+ [self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
|
|
|
+ return;
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
// Continue download process
|
|
|
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
|
|
|
}];
|
|
@@ -300,16 +336,17 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
context:(nullable SDWebImageContext *)context
|
|
|
progress:(nullable SDImageLoaderProgressBlock)progressBlock
|
|
|
completed:(nullable SDInternalCompletionBlock)completedBlock {
|
|
|
- // Grab the image cache to use
|
|
|
- id<SDImageCache> imageCache;
|
|
|
- if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
|
|
|
+ // Grab the image cache to use, choose standalone original cache firstly
|
|
|
+ id<SDImageCache> imageCache = context[SDWebImageContextOriginalImageCache];
|
|
|
+ if (!imageCache) {
|
|
|
+ // if no standalone cache available, use default cache
|
|
|
imageCache = context[SDWebImageContextImageCache];
|
|
|
- } else {
|
|
|
- imageCache = self.imageCache;
|
|
|
+ if (!imageCache) {
|
|
|
+ imageCache = self.imageCache;
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
// Get the original query cache type
|
|
|
- SDImageCacheType originalQueryCacheType = SDImageCacheTypeNone;
|
|
|
+ SDImageCacheType originalQueryCacheType = SDImageCacheTypeDisk;
|
|
|
if (context[SDWebImageContextOriginalQueryCacheType]) {
|
|
|
originalQueryCacheType = [context[SDWebImageContextOriginalQueryCacheType] integerValue];
|
|
|
}
|
|
@@ -317,42 +354,30 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
// Check whether we should query original cache
|
|
|
BOOL shouldQueryOriginalCache = (originalQueryCacheType != SDImageCacheTypeNone);
|
|
|
if (shouldQueryOriginalCache) {
|
|
|
- // Change originContext to mutable
|
|
|
- SDWebImageMutableContext * __block originContext;
|
|
|
- if (context) {
|
|
|
- originContext = [context mutableCopy];
|
|
|
- } else {
|
|
|
- originContext = [NSMutableDictionary dictionary];
|
|
|
- }
|
|
|
-
|
|
|
- // Disable transformer for cache key generation
|
|
|
- id<SDImageTransformer> transformer = originContext[SDWebImageContextImageTransformer];
|
|
|
- originContext[SDWebImageContextImageTransformer] = [NSNull null];
|
|
|
-
|
|
|
- NSString *key = [self cacheKeyForURL:url context:originContext];
|
|
|
+ // Get original cache key generation without transformer
|
|
|
+ NSString *key = [self originalCacheKeyForURL:url context:context];
|
|
|
@weakify(operation);
|
|
|
operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:originalQueryCacheType 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:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url];
|
|
|
+ [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] queue:context[SDWebImageContextCallbackQueue] url:url];
|
|
|
[self safelyRemoveOperationFromRunning:operation];
|
|
|
return;
|
|
|
+ } else if (!cachedImage) {
|
|
|
+ // Original image cache miss. Continue download process
|
|
|
+ [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
|
|
|
+ return;
|
|
|
}
|
|
|
-
|
|
|
- // Add original transformer
|
|
|
- if (transformer) {
|
|
|
- originContext[SDWebImageContextImageTransformer] = transformer;
|
|
|
- }
|
|
|
-
|
|
|
- // Use the store cache process instead of downloading, and ignore .refreshCached option for now
|
|
|
- [self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:cachedImage downloadedData:cachedData finished:YES progress:progressBlock completed:completedBlock];
|
|
|
+
|
|
|
+ // Skip downloading and continue transform process, and ignore .refreshCached option for now
|
|
|
+ [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:cachedImage originalData:cachedData cacheType:cacheType finished:YES completed:completedBlock];
|
|
|
|
|
|
[self safelyRemoveOperationFromRunning:operation];
|
|
|
}];
|
|
|
} else {
|
|
|
// Continue download process
|
|
|
- [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:originalQueryCacheType progress:progressBlock completed:completedBlock];
|
|
|
+ [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -366,11 +391,14 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
cacheType:(SDImageCacheType)cacheType
|
|
|
progress:(nullable SDImageLoaderProgressBlock)progressBlock
|
|
|
completed:(nullable SDInternalCompletionBlock)completedBlock {
|
|
|
+ // Mark the cache operation end
|
|
|
+ @synchronized (operation) {
|
|
|
+ operation.cacheOperation = nil;
|
|
|
+ }
|
|
|
+
|
|
|
// Grab the image loader to use
|
|
|
- id<SDImageLoader> imageLoader;
|
|
|
- if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
|
|
|
- imageLoader = context[SDWebImageContextImageLoader];
|
|
|
- } else {
|
|
|
+ id<SDImageLoader> imageLoader = context[SDWebImageContextImageLoader];
|
|
|
+ if (!imageLoader) {
|
|
|
imageLoader = self.imageLoader;
|
|
|
}
|
|
|
|
|
@@ -378,12 +406,16 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
|
|
|
shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
|
|
|
shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
|
|
|
- shouldDownload &= [imageLoader canRequestImageForURL:url];
|
|
|
+ if ([imageLoader respondsToSelector:@selector(canRequestImageForURL:options:context:)]) {
|
|
|
+ shouldDownload &= [imageLoader canRequestImageForURL:url options:options context:context];
|
|
|
+ } else {
|
|
|
+ shouldDownload &= [imageLoader canRequestImageForURL:url];
|
|
|
+ }
|
|
|
if (shouldDownload) {
|
|
|
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.
|
|
|
- [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
|
|
|
+ [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES queue:context[SDWebImageContextCallbackQueue] url:url];
|
|
|
// Pass the cached image to the image loader. The image loader should check whether the remote image is equal to the cached image.
|
|
|
SDWebImageMutableContext *mutableContext;
|
|
|
if (context) {
|
|
@@ -400,29 +432,29 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
@strongify(operation);
|
|
|
if (!operation || operation.isCancelled) {
|
|
|
// Image combined operation cancelled by user
|
|
|
- [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] url:url];
|
|
|
+ [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] queue:context[SDWebImageContextCallbackQueue] url:url];
|
|
|
} else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
|
|
|
// Image refresh hit the NSURLCache cache, do not call the completion block
|
|
|
} else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) {
|
|
|
// Download operation cancelled by user before sending the request, don't block failed URL
|
|
|
- [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
|
|
|
+ [self callCompletionBlockForOperation:operation completion:completedBlock error:error queue:context[SDWebImageContextCallbackQueue] url:url];
|
|
|
} else if (error) {
|
|
|
- [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
|
|
|
+ [self callCompletionBlockForOperation:operation completion:completedBlock error:error queue:context[SDWebImageContextCallbackQueue] url:url];
|
|
|
BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context];
|
|
|
|
|
|
if (shouldBlockFailedURL) {
|
|
|
- SD_LOCK(self.failedURLsLock);
|
|
|
+ SD_LOCK(self->_failedURLsLock);
|
|
|
[self.failedURLs addObject:url];
|
|
|
- SD_UNLOCK(self.failedURLsLock);
|
|
|
+ SD_UNLOCK(self->_failedURLsLock);
|
|
|
}
|
|
|
} else {
|
|
|
if ((options & SDWebImageRetryFailed)) {
|
|
|
- SD_LOCK(self.failedURLsLock);
|
|
|
+ SD_LOCK(self->_failedURLsLock);
|
|
|
[self.failedURLs removeObject:url];
|
|
|
- SD_UNLOCK(self.failedURLsLock);
|
|
|
+ SD_UNLOCK(self->_failedURLsLock);
|
|
|
}
|
|
|
- // Continue store cache process
|
|
|
- [self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
|
|
|
+ // Continue transform process
|
|
|
+ [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:SDImageCacheTypeNone finished:finished completed:completedBlock];
|
|
|
}
|
|
|
|
|
|
if (finished) {
|
|
@@ -430,127 +462,152 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
}
|
|
|
}];
|
|
|
} else if (cachedImage) {
|
|
|
- [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
|
|
|
+ [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES queue:context[SDWebImageContextCallbackQueue] url:url];
|
|
|
[self safelyRemoveOperationFromRunning:operation];
|
|
|
} else {
|
|
|
// Image not in cache and download disallowed by delegate
|
|
|
- [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
|
|
|
+ [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES queue:context[SDWebImageContextCallbackQueue] url:url];
|
|
|
[self safelyRemoveOperationFromRunning:operation];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// Store cache process
|
|
|
-- (void)callStoreCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
|
|
|
- url:(nonnull NSURL *)url
|
|
|
- options:(SDWebImageOptions)options
|
|
|
- context:(SDWebImageContext *)context
|
|
|
- downloadedImage:(nullable UIImage *)downloadedImage
|
|
|
- downloadedData:(nullable NSData *)downloadedData
|
|
|
- finished:(BOOL)finished
|
|
|
- progress:(nullable SDImageLoaderProgressBlock)progressBlock
|
|
|
- completed:(nullable SDInternalCompletionBlock)completedBlock {
|
|
|
- // the target image store cache type
|
|
|
- SDImageCacheType storeCacheType = SDImageCacheTypeAll;
|
|
|
- if (context[SDWebImageContextStoreCacheType]) {
|
|
|
- storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
|
|
|
+// Transform process
|
|
|
+- (void)callTransformProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
|
|
|
+ url:(nonnull NSURL *)url
|
|
|
+ options:(SDWebImageOptions)options
|
|
|
+ context:(SDWebImageContext *)context
|
|
|
+ originalImage:(nullable UIImage *)originalImage
|
|
|
+ originalData:(nullable NSData *)originalData
|
|
|
+ cacheType:(SDImageCacheType)cacheType
|
|
|
+ finished:(BOOL)finished
|
|
|
+ completed:(nullable SDInternalCompletionBlock)completedBlock {
|
|
|
+ id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
|
|
|
+ if ([transformer isEqual:NSNull.null]) {
|
|
|
+ transformer = nil;
|
|
|
+ }
|
|
|
+ // transformer check
|
|
|
+ BOOL shouldTransformImage = originalImage && transformer;
|
|
|
+ shouldTransformImage = shouldTransformImage && (!originalImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
|
|
|
+ shouldTransformImage = shouldTransformImage && (!originalImage.sd_isVector || (options & SDWebImageTransformVectorImage));
|
|
|
+ // thumbnail check
|
|
|
+ BOOL isThumbnail = originalImage.sd_isThumbnail;
|
|
|
+ NSData *cacheData = originalData;
|
|
|
+ UIImage *cacheImage = originalImage;
|
|
|
+ if (isThumbnail) {
|
|
|
+ cacheData = nil; // thumbnail don't store full size data
|
|
|
+ originalImage = nil; // thumbnail don't have full size image
|
|
|
+ }
|
|
|
+
|
|
|
+ if (shouldTransformImage) {
|
|
|
+ // transformed cache key
|
|
|
+ NSString *key = [self cacheKeyForURL:url context:context];
|
|
|
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
|
|
+ // Case that transformer on thumbnail, which this time need full pixel image
|
|
|
+ UIImage *transformedImage = [transformer transformedImageWithImage:cacheImage forKey:key];
|
|
|
+ if (transformedImage) {
|
|
|
+ transformedImage.sd_isTransformed = YES;
|
|
|
+ [self callStoreOriginCacheProcessForOperation:operation url:url options:options context:context originalImage:originalImage cacheImage:transformedImage originalData:originalData cacheData:nil cacheType:cacheType finished:finished completed:completedBlock];
|
|
|
+ } else {
|
|
|
+ [self callStoreOriginCacheProcessForOperation:operation url:url options:options context:context originalImage:originalImage cacheImage:cacheImage originalData:originalData cacheData:cacheData cacheType:cacheType finished:finished completed:completedBlock];
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ [self callStoreOriginCacheProcessForOperation:operation url:url options:options context:context originalImage:originalImage cacheImage:cacheImage originalData:originalData cacheData:cacheData cacheType:cacheType finished:finished completed:completedBlock];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Store origin cache process
|
|
|
+- (void)callStoreOriginCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
|
|
|
+ url:(nonnull NSURL *)url
|
|
|
+ options:(SDWebImageOptions)options
|
|
|
+ context:(SDWebImageContext *)context
|
|
|
+ originalImage:(nullable UIImage *)originalImage
|
|
|
+ cacheImage:(nullable UIImage *)cacheImage
|
|
|
+ originalData:(nullable NSData *)originalData
|
|
|
+ cacheData:(nullable NSData *)cacheData
|
|
|
+ cacheType:(SDImageCacheType)cacheType
|
|
|
+ finished:(BOOL)finished
|
|
|
+ completed:(nullable SDInternalCompletionBlock)completedBlock {
|
|
|
+ // Grab the image cache to use, choose standalone original cache firstly
|
|
|
+ id<SDImageCache> imageCache = context[SDWebImageContextOriginalImageCache];
|
|
|
+ if (!imageCache) {
|
|
|
+ // if no standalone cache available, use default cache
|
|
|
+ imageCache = context[SDWebImageContextImageCache];
|
|
|
+ if (!imageCache) {
|
|
|
+ imageCache = self.imageCache;
|
|
|
+ }
|
|
|
}
|
|
|
// the original store image cache type
|
|
|
- SDImageCacheType originalStoreCacheType = SDImageCacheTypeNone;
|
|
|
+ SDImageCacheType originalStoreCacheType = SDImageCacheTypeDisk;
|
|
|
if (context[SDWebImageContextOriginalStoreCacheType]) {
|
|
|
originalStoreCacheType = [context[SDWebImageContextOriginalStoreCacheType] integerValue];
|
|
|
}
|
|
|
- // origin cache key
|
|
|
- SDWebImageMutableContext *originContext = [context mutableCopy];
|
|
|
- // disable transformer for cache key generation
|
|
|
- originContext[SDWebImageContextImageTransformer] = [NSNull null];
|
|
|
- NSString *key = [self cacheKeyForURL:url context:originContext];
|
|
|
- id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
|
|
|
- if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
|
|
|
- transformer = nil;
|
|
|
- }
|
|
|
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
|
|
|
|
|
|
- BOOL shouldTransformImage = downloadedImage && transformer;
|
|
|
- shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
|
|
|
- shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isVector || (options & SDWebImageTransformVectorImage));
|
|
|
- BOOL shouldCacheOriginal = downloadedImage && finished;
|
|
|
+ // If the original cacheType is disk, since we don't need to store the original data again
|
|
|
+ // Strip the disk from the originalStoreCacheType
|
|
|
+ if (cacheType == SDImageCacheTypeDisk) {
|
|
|
+ if (originalStoreCacheType == SDImageCacheTypeDisk) originalStoreCacheType = SDImageCacheTypeNone;
|
|
|
+ if (originalStoreCacheType == SDImageCacheTypeAll) originalStoreCacheType = SDImageCacheTypeMemory;
|
|
|
+ }
|
|
|
|
|
|
- // if available, store original image to cache
|
|
|
- if (shouldCacheOriginal) {
|
|
|
- // normally use the store cache type, but if target image is transformed, use original store cache type instead
|
|
|
- SDImageCacheType targetStoreCacheType = shouldTransformImage ? originalStoreCacheType : storeCacheType;
|
|
|
- if (cacheSerializer && (targetStoreCacheType == SDImageCacheTypeDisk || targetStoreCacheType == SDImageCacheTypeAll)) {
|
|
|
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
|
|
- @autoreleasepool {
|
|
|
- NSData *cacheData = [cacheSerializer cacheDataWithImage:downloadedImage originalData:downloadedData imageURL:url];
|
|
|
- [self storeImage:downloadedImage imageData:cacheData forKey:key cacheType:targetStoreCacheType options:options context:context completion:^{
|
|
|
- // Continue transform process
|
|
|
- [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
|
|
|
- }];
|
|
|
- }
|
|
|
- });
|
|
|
- } else {
|
|
|
- [self storeImage:downloadedImage imageData:downloadedData forKey:key cacheType:targetStoreCacheType options:options context:context completion:^{
|
|
|
- // Continue transform process
|
|
|
- [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
|
|
|
+ // Get original cache key generation without transformer
|
|
|
+ NSString *key = [self originalCacheKeyForURL:url context:context];
|
|
|
+ if (finished && cacheSerializer && (originalStoreCacheType == SDImageCacheTypeDisk || originalStoreCacheType == SDImageCacheTypeAll)) {
|
|
|
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
|
|
+ NSData *newOriginalData = [cacheSerializer cacheDataWithImage:originalImage originalData:originalData imageURL:url];
|
|
|
+ // Store original image and data
|
|
|
+ [self storeImage:originalImage imageData:newOriginalData forKey:key options:options context:context imageCache:imageCache cacheType:originalStoreCacheType finished:finished completion:^{
|
|
|
+ // Continue store cache process, transformed data is nil
|
|
|
+ [self callStoreCacheProcessForOperation:operation url:url options:options context:context image:cacheImage data:cacheData cacheType:cacheType finished:finished completed:completedBlock];
|
|
|
}];
|
|
|
- }
|
|
|
+ });
|
|
|
} else {
|
|
|
- // Continue transform process
|
|
|
- [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
|
|
|
+ // Store original image and data
|
|
|
+ [self storeImage:originalImage imageData:originalData forKey:key options:options context:context imageCache:imageCache cacheType:originalStoreCacheType finished:finished completion:^{
|
|
|
+ // Continue store cache process, transformed data is nil
|
|
|
+ [self callStoreCacheProcessForOperation:operation url:url options:options context:context image:cacheImage data:cacheData cacheType:cacheType finished:finished completed:completedBlock];
|
|
|
+ }];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// Transform process
|
|
|
-- (void)callTransformProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
|
|
|
- url:(nonnull NSURL *)url
|
|
|
- options:(SDWebImageOptions)options
|
|
|
- context:(SDWebImageContext *)context
|
|
|
- originalImage:(nullable UIImage *)originalImage
|
|
|
- originalData:(nullable NSData *)originalData
|
|
|
- finished:(BOOL)finished
|
|
|
- progress:(nullable SDImageLoaderProgressBlock)progressBlock
|
|
|
- completed:(nullable SDInternalCompletionBlock)completedBlock {
|
|
|
+// Store normal cache process
|
|
|
+- (void)callStoreCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
|
|
|
+ url:(nonnull NSURL *)url
|
|
|
+ options:(SDWebImageOptions)options
|
|
|
+ context:(SDWebImageContext *)context
|
|
|
+ image:(nullable UIImage *)image
|
|
|
+ data:(nullable NSData *)data
|
|
|
+ cacheType:(SDImageCacheType)cacheType
|
|
|
+ finished:(BOOL)finished
|
|
|
+ completed:(nullable SDInternalCompletionBlock)completedBlock {
|
|
|
+ // Grab the image cache to use
|
|
|
+ id<SDImageCache> imageCache = context[SDWebImageContextImageCache];
|
|
|
+ if (!imageCache) {
|
|
|
+ imageCache = self.imageCache;
|
|
|
+ }
|
|
|
// the target image store cache type
|
|
|
SDImageCacheType storeCacheType = SDImageCacheTypeAll;
|
|
|
if (context[SDWebImageContextStoreCacheType]) {
|
|
|
storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
|
|
|
}
|
|
|
- // transformed cache key
|
|
|
- NSString *key = [self cacheKeyForURL:url context:context];
|
|
|
- id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
|
|
|
- if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
|
|
|
- transformer = nil;
|
|
|
- }
|
|
|
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
|
|
|
|
|
|
- BOOL shouldTransformImage = originalImage && transformer;
|
|
|
- shouldTransformImage = shouldTransformImage && (!originalImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
|
|
|
- shouldTransformImage = shouldTransformImage && (!originalImage.sd_isVector || (options & SDWebImageTransformVectorImage));
|
|
|
- // if available, store transformed image to cache
|
|
|
- if (shouldTransformImage) {
|
|
|
+ // transformed cache key
|
|
|
+ NSString *key = [self cacheKeyForURL:url context:context];
|
|
|
+ if (finished && cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
|
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
|
|
- @autoreleasepool {
|
|
|
- UIImage *transformedImage = [transformer transformedImageWithImage:originalImage forKey:key];
|
|
|
- if (transformedImage && finished) {
|
|
|
- BOOL imageWasTransformed = ![transformedImage isEqual:originalImage];
|
|
|
- NSData *cacheData;
|
|
|
- // pass nil if the image was transformed, so we can recalculate the data from the image
|
|
|
- if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
|
|
|
- cacheData = [cacheSerializer cacheDataWithImage:transformedImage originalData:(imageWasTransformed ? nil : originalData) imageURL:url];
|
|
|
- } else {
|
|
|
- cacheData = (imageWasTransformed ? nil : originalData);
|
|
|
- }
|
|
|
- [self storeImage:transformedImage imageData:cacheData forKey:key cacheType:storeCacheType options:options context:context completion:^{
|
|
|
- [self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
|
|
|
- }];
|
|
|
- } else {
|
|
|
- [self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
|
|
|
- }
|
|
|
- }
|
|
|
+ NSData *newData = [cacheSerializer cacheDataWithImage:image originalData:data imageURL:url];
|
|
|
+ // Store image and data
|
|
|
+ [self storeImage:image imageData:newData forKey:key options:options context:context imageCache:imageCache cacheType:storeCacheType finished:finished completion:^{
|
|
|
+ [self callCompletionBlockForOperation:operation completion:completedBlock image:image data:data error:nil cacheType:cacheType finished:finished queue:context[SDWebImageContextCallbackQueue] url:url];
|
|
|
+ }];
|
|
|
});
|
|
|
} else {
|
|
|
- [self callCompletionBlockForOperation:operation completion:completedBlock image:originalImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
|
|
|
+ // Store image and data
|
|
|
+ [self storeImage:image imageData:data forKey:key options:options context:context imageCache:imageCache cacheType:storeCacheType finished:finished completion:^{
|
|
|
+ [self callCompletionBlockForOperation:operation completion:completedBlock image:image data:data error:nil cacheType:cacheType finished:finished queue:context[SDWebImageContextCallbackQueue] url:url];
|
|
|
+ }];
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -560,33 +617,46 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
if (!operation) {
|
|
|
return;
|
|
|
}
|
|
|
- SD_LOCK(self.runningOperationsLock);
|
|
|
+ SD_LOCK(_runningOperationsLock);
|
|
|
[self.runningOperations removeObject:operation];
|
|
|
- SD_UNLOCK(self.runningOperationsLock);
|
|
|
+ SD_UNLOCK(_runningOperationsLock);
|
|
|
}
|
|
|
|
|
|
- (void)storeImage:(nullable UIImage *)image
|
|
|
imageData:(nullable NSData *)data
|
|
|
forKey:(nullable NSString *)key
|
|
|
- cacheType:(SDImageCacheType)cacheType
|
|
|
options:(SDWebImageOptions)options
|
|
|
context:(nullable SDWebImageContext *)context
|
|
|
+ imageCache:(nonnull id<SDImageCache>)imageCache
|
|
|
+ cacheType:(SDImageCacheType)cacheType
|
|
|
+ finished:(BOOL)finished
|
|
|
completion:(nullable SDWebImageNoParamsBlock)completion {
|
|
|
- id<SDImageCache> imageCache;
|
|
|
- if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
|
|
|
- imageCache = context[SDWebImageContextImageCache];
|
|
|
- } else {
|
|
|
- imageCache = self.imageCache;
|
|
|
- }
|
|
|
BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
|
|
|
+ // Ignore progressive data cache
|
|
|
+ if (!finished) {
|
|
|
+ if (completion) {
|
|
|
+ completion();
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
// Check whether we should wait the store cache finished. If not, callback immediately
|
|
|
- [imageCache storeImage:image imageData:data forKey:key cacheType:cacheType completion:^{
|
|
|
- if (waitStoreCache) {
|
|
|
- if (completion) {
|
|
|
- completion();
|
|
|
+ if ([imageCache respondsToSelector:@selector(storeImage:imageData:forKey:options:context:cacheType:completion:)]) {
|
|
|
+ [imageCache storeImage:image imageData:data forKey:key options:options context:context cacheType:cacheType completion:^{
|
|
|
+ if (waitStoreCache) {
|
|
|
+ if (completion) {
|
|
|
+ completion();
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- }];
|
|
|
+ }];
|
|
|
+ } else {
|
|
|
+ [imageCache storeImage:image imageData:data forKey:key cacheType:cacheType completion:^{
|
|
|
+ if (waitStoreCache) {
|
|
|
+ if (completion) {
|
|
|
+ completion();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }];
|
|
|
+ }
|
|
|
if (!waitStoreCache) {
|
|
|
if (completion) {
|
|
|
completion();
|
|
@@ -597,8 +667,9 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
|
|
|
completion:(nullable SDInternalCompletionBlock)completionBlock
|
|
|
error:(nullable NSError *)error
|
|
|
+ queue:(nullable SDCallbackQueue *)queue
|
|
|
url:(nullable NSURL *)url {
|
|
|
- [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url];
|
|
|
+ [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES queue:queue url:url];
|
|
|
}
|
|
|
|
|
|
- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
|
|
@@ -608,22 +679,21 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
error:(nullable NSError *)error
|
|
|
cacheType:(SDImageCacheType)cacheType
|
|
|
finished:(BOOL)finished
|
|
|
+ queue:(nullable SDCallbackQueue *)queue
|
|
|
url:(nullable NSURL *)url {
|
|
|
- dispatch_main_async_safe(^{
|
|
|
- if (completionBlock) {
|
|
|
+ if (completionBlock) {
|
|
|
+ [(queue ?: SDCallbackQueue.mainQueue) async:^{
|
|
|
completionBlock(image, data, error, cacheType, finished, url);
|
|
|
- }
|
|
|
- });
|
|
|
+ }];
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url
|
|
|
error:(nonnull NSError *)error
|
|
|
options:(SDWebImageOptions)options
|
|
|
context:(nullable SDWebImageContext *)context {
|
|
|
- id<SDImageLoader> imageLoader;
|
|
|
- if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
|
|
|
- imageLoader = context[SDWebImageContextImageLoader];
|
|
|
- } else {
|
|
|
+ id<SDImageLoader> imageLoader = context[SDWebImageContextImageLoader];
|
|
|
+ if (!imageLoader) {
|
|
|
imageLoader = self.imageLoader;
|
|
|
}
|
|
|
// Check whether we should block failed url
|
|
@@ -631,7 +701,11 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) {
|
|
|
shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error];
|
|
|
} else {
|
|
|
- shouldBlockFailedURL = [imageLoader shouldBlockFailedURLWithURL:url error:error];
|
|
|
+ if ([imageLoader respondsToSelector:@selector(shouldBlockFailedURLWithURL:error:options:context:)]) {
|
|
|
+ shouldBlockFailedURL = [imageLoader shouldBlockFailedURLWithURL:url error:error options:options context:context];
|
|
|
+ } else {
|
|
|
+ shouldBlockFailedURL = [imageLoader shouldBlockFailedURLWithURL:url error:error];
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
return shouldBlockFailedURL;
|
|
@@ -681,12 +755,20 @@ static id<SDImageLoader> _defaultImageLoader;
|
|
|
|
|
|
@implementation SDWebImageCombinedOperation
|
|
|
|
|
|
+- (BOOL)isCancelled {
|
|
|
+ // Need recursive lock (user's cancel block may check isCancelled), do not use SD_LOCK
|
|
|
+ @synchronized (self) {
|
|
|
+ return _cancelled;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
- (void)cancel {
|
|
|
+ // Need recursive lock (user's cancel block may check isCancelled), do not use SD_LOCK
|
|
|
@synchronized(self) {
|
|
|
- if (self.isCancelled) {
|
|
|
+ if (_cancelled) {
|
|
|
return;
|
|
|
}
|
|
|
- self.cancelled = YES;
|
|
|
+ _cancelled = YES;
|
|
|
if (self.cacheOperation) {
|
|
|
[self.cacheOperation cancel];
|
|
|
self.cacheOperation = nil;
|