123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825 |
- /*
- * This file is part of the SDWebImage package.
- * (c) Olivier Poitrey <rs@dailymotion.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- #import "SDWebImageManager.h"
- #import "SDImageCache.h"
- #import "SDWebImageDownloader.h"
- #import "UIImage+Metadata.h"
- #import "SDAssociatedObject.h"
- #import "SDWebImageError.h"
- #import "SDInternalMacros.h"
- static id<SDImageCache> _defaultImageCache;
- static id<SDImageLoader> _defaultImageLoader;
- @interface SDWebImageCombinedOperation ()
- @property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
- @property (strong, nonatomic, readwrite, nullable) id<SDWebImageOperation> loaderOperation;
- @property (strong, nonatomic, readwrite, nullable) id<SDWebImageOperation> cacheOperation;
- @property (weak, nonatomic, nullable) SDWebImageManager *manager;
- @end
- @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) NSMutableSet<SDWebImageCombinedOperation *> *runningOperations;
- @end
- @implementation SDWebImageManager
- + (id<SDImageCache>)defaultImageCache {
- return _defaultImageCache;
- }
- + (void)setDefaultImageCache:(id<SDImageCache>)defaultImageCache {
- if (defaultImageCache && ![defaultImageCache conformsToProtocol:@protocol(SDImageCache)]) {
- return;
- }
- _defaultImageCache = defaultImageCache;
- }
- + (id<SDImageLoader>)defaultImageLoader {
- return _defaultImageLoader;
- }
- + (void)setDefaultImageLoader:(id<SDImageLoader>)defaultImageLoader {
- if (defaultImageLoader && ![defaultImageLoader conformsToProtocol:@protocol(SDImageLoader)]) {
- return;
- }
- _defaultImageLoader = defaultImageLoader;
- }
- + (nonnull instancetype)sharedManager {
- static dispatch_once_t once;
- static id instance;
- dispatch_once(&once, ^{
- instance = [self new];
- });
- return instance;
- }
- - (nonnull instancetype)init {
- id<SDImageCache> cache = [[self class] defaultImageCache];
- if (!cache) {
- cache = [SDImageCache sharedImageCache];
- }
- id<SDImageLoader> loader = [[self class] defaultImageLoader];
- if (!loader) {
- loader = [SDWebImageDownloader sharedDownloader];
- }
- return [self initWithCache:cache loader:loader];
- }
- - (nonnull instancetype)initWithCache:(nonnull id<SDImageCache>)cache loader:(nonnull id<SDImageLoader>)loader {
- if ((self = [super init])) {
- _imageCache = cache;
- _imageLoader = loader;
- _failedURLs = [NSMutableSet new];
- SD_LOCK_INIT(_failedURLsLock);
- _runningOperations = [NSMutableSet new];
- SD_LOCK_INIT(_runningOperationsLock);
- }
- return self;
- }
- - (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
- if (!url) {
- return @"";
- }
-
- NSString *key;
- // Cache Key Filter
- id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;
- if (cacheKeyFilter) {
- key = [cacheKeyFilter cacheKeyForURL:url];
- } else {
- key = url.absoluteString;
- }
-
- 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 @"";
- }
-
- 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;
- }
-
- // Thumbnail Key Appending
- NSValue *thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
- if (thumbnailSizeValue != nil) {
- CGSize thumbnailSize = CGSizeZero;
- #if SD_MAC
- thumbnailSize = thumbnailSizeValue.sizeValue;
- #else
- thumbnailSize = thumbnailSizeValue.CGSizeValue;
- #endif
- BOOL preserveAspectRatio = YES;
- NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
- if (preserveAspectRatioValue != nil) {
- preserveAspectRatio = preserveAspectRatioValue.boolValue;
- }
- key = SDThumbnailedKeyForKey(key, thumbnailSize, preserveAspectRatio);
- }
-
- // Transformer Key Appending
- id<SDImageTransformer> transformer = self.transformer;
- if (context[SDWebImageContextImageTransformer]) {
- transformer = context[SDWebImageContextImageTransformer];
- if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
- transformer = nil;
- }
- }
- if (transformer) {
- key = SDTransformedKeyForKey(key, transformer.transformerKey);
- }
-
- return key;
- }
- - (SDWebImageCombinedOperation *)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDInternalCompletionBlock)completedBlock {
- return [self loadImageWithURL:url options:options context:nil progress:progressBlock completed:completedBlock];
- }
- - (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;
- }
- SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
- operation.manager = self;
- BOOL isFailedUrl = NO;
- if (url) {
- SD_LOCK(_failedURLsLock);
- isFailedUrl = [self.failedURLs containsObject:url];
- SD_UNLOCK(_failedURLsLock);
- }
- 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];
- return operation;
- }
- SD_LOCK(_runningOperationsLock);
- [self.runningOperations addObject:operation];
- 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
- [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
- return operation;
- }
- - (void)cancelAll {
- SD_LOCK(_runningOperationsLock);
- NSSet<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy];
- SD_UNLOCK(_runningOperationsLock);
- [copiedOperations makeObjectsPerformSelector:@selector(cancel)]; // This will call `safelyRemoveOperationFromRunning:` and remove from the array
- }
- - (BOOL)isRunning {
- BOOL isRunning = NO;
- SD_LOCK(_runningOperationsLock);
- isRunning = (self.runningOperations.count > 0);
- SD_UNLOCK(_runningOperationsLock);
- return isRunning;
- }
- - (void)removeFailedURL:(NSURL *)url {
- if (!url) {
- return;
- }
- SD_LOCK(_failedURLsLock);
- [self.failedURLs removeObject:url];
- SD_UNLOCK(_failedURLsLock);
- }
- - (void)removeAllFailedURLs {
- SD_LOCK(_failedURLsLock);
- [self.failedURLs removeAllObjects];
- SD_UNLOCK(_failedURLsLock);
- }
- #pragma mark - Private
- // Query normal 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 {
- // Grab the image cache to use
- id<SDImageCache> imageCache;
- if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
- imageCache = context[SDWebImageContextImageCache];
- } else {
- imageCache = self.imageCache;
- }
- // Get the query cache type
- SDImageCacheType queryCacheType = SDImageCacheTypeAll;
- if (context[SDWebImageContextQueryCacheType]) {
- queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue];
- }
-
- // Check whether we should query cache
- BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
- if (shouldQueryCache) {
- 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 safelyRemoveOperationFromRunning:operation];
- return;
- } else if (!cachedImage) {
- BOOL mayInOriginalCache = context[SDWebImageContextImageTransformer] || context[SDWebImageContextImageThumbnailPixelSize];
- // 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];
- }];
- } else {
- // Continue download process
- [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
- }
- }
- // Query original cache process
- - (void)callOriginalCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
- url:(nonnull NSURL *)url
- options:(SDWebImageOptions)options
- context:(nullable SDWebImageContext *)context
- progress:(nullable SDImageLoaderProgressBlock)progressBlock
- completed:(nullable SDInternalCompletionBlock)completedBlock {
- // Grab the image cache to use, choose standalone original cache firstly
- id<SDImageCache> imageCache;
- if ([context[SDWebImageContextOriginalImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
- imageCache = context[SDWebImageContextOriginalImageCache];
- } else {
- // if no standalone cache available, use default cache
- if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
- imageCache = context[SDWebImageContextImageCache];
- } else {
- imageCache = self.imageCache;
- }
- }
- // Get the original query cache type
- SDImageCacheType originalQueryCacheType = SDImageCacheTypeDisk;
- if (context[SDWebImageContextOriginalQueryCacheType]) {
- originalQueryCacheType = [context[SDWebImageContextOriginalQueryCacheType] integerValue];
- }
-
- // Check whether we should query original cache
- BOOL shouldQueryOriginalCache = (originalQueryCacheType != SDImageCacheTypeNone);
- if (shouldQueryOriginalCache) {
- // Get original cache key generation without transformer/thumbnail
- 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 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;
- }
-
- // 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 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:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
- }
- }
- // Download process
- - (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
- url:(nonnull NSURL *)url
- options:(SDWebImageOptions)options
- context:(SDWebImageContext *)context
- cachedImage:(nullable UIImage *)cachedImage
- cachedData:(nullable NSData *)cachedData
- cacheType:(SDImageCacheType)cacheType
- progress:(nullable SDImageLoaderProgressBlock)progressBlock
- completed:(nullable SDInternalCompletionBlock)completedBlock {
- // Grab the image loader to use
- id<SDImageLoader> imageLoader;
- if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
- imageLoader = context[SDWebImageContextImageLoader];
- } else {
- imageLoader = self.imageLoader;
- }
-
- // Check whether we should download image from network
- BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
- shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
- shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL: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];
- // 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) {
- mutableContext = [context mutableCopy];
- } else {
- mutableContext = [NSMutableDictionary dictionary];
- }
- mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
- context = [mutableContext copy];
- }
-
- @weakify(operation);
- operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
- @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];
- } 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];
- } else if (error) {
- [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
- BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context];
-
- if (shouldBlockFailedURL) {
- SD_LOCK(self->_failedURLsLock);
- [self.failedURLs addObject:url];
- SD_UNLOCK(self->_failedURLsLock);
- }
- } else {
- if ((options & SDWebImageRetryFailed)) {
- SD_LOCK(self->_failedURLsLock);
- [self.failedURLs removeObject:url];
- SD_UNLOCK(self->_failedURLsLock);
- }
- // Continue store cache process
- [self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData cacheType:SDImageCacheTypeNone finished:finished completed:completedBlock];
- }
-
- if (finished) {
- [self safelyRemoveOperationFromRunning:operation];
- }
- }];
- } else if (cachedImage) {
- [self callCompletionBlockForOperation:operation 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
- [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES 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
- cacheType:(SDImageCacheType)cacheType
- finished:(BOOL)finished
- completed:(nullable SDInternalCompletionBlock)completedBlock {
- // Grab the image cache to use, choose standalone original cache firstly
- id<SDImageCache> imageCache;
- if ([context[SDWebImageContextOriginalImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
- imageCache = context[SDWebImageContextOriginalImageCache];
- } else {
- // if no standalone cache available, use default cache
- if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
- imageCache = context[SDWebImageContextImageCache];
- } else {
- imageCache = self.imageCache;
- }
- }
- BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
- // the target image store cache type
- SDImageCacheType storeCacheType = SDImageCacheTypeAll;
- if (context[SDWebImageContextStoreCacheType]) {
- storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
- }
- // the original store image cache type
- SDImageCacheType originalStoreCacheType = SDImageCacheTypeDisk;
- if (context[SDWebImageContextOriginalStoreCacheType]) {
- originalStoreCacheType = [context[SDWebImageContextOriginalStoreCacheType] integerValue];
- }
- id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
- if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
- transformer = nil;
- }
- id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
-
- // transformer check
- BOOL shouldTransformImage = downloadedImage && transformer;
- shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
- shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isVector || (options & SDWebImageTransformVectorImage));
- // thumbnail check
- BOOL shouldThumbnailImage = context[SDWebImageContextImageThumbnailPixelSize] != nil || downloadedImage.sd_decodeOptions[SDImageCoderDecodeThumbnailPixelSize] != nil;
-
- BOOL shouldCacheOriginal = downloadedImage && finished && cacheType == SDImageCacheTypeNone;
-
- // if available, store original image to cache
- if (shouldCacheOriginal) {
- // Get original cache key generation without transformer/thumbnail
- NSString *key = [self originalCacheKeyForURL:url context:context];
- // normally use the store cache type, but if target image is transformed, use original store cache type instead
- SDImageCacheType targetStoreCacheType = (shouldTransformImage || shouldThumbnailImage) ? originalStoreCacheType : storeCacheType;
- UIImage *fullSizeImage = downloadedImage;
- if (shouldThumbnailImage) {
- // Thumbnail decoding does not keep original image
- // Here we only store the original data to disk for original cache key
- // Store thumbnail image to memory for thumbnail cache key later in `storeTransformCacheProcess`
- fullSizeImage = nil;
- }
- if (fullSizeImage && cacheSerializer && (targetStoreCacheType == SDImageCacheTypeDisk || targetStoreCacheType == SDImageCacheTypeAll)) {
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
- @autoreleasepool {
- NSData *cacheData = [cacheSerializer cacheDataWithImage:fullSizeImage originalData:downloadedData imageURL:url];
- [self storeImage:fullSizeImage imageData:cacheData forKey:key imageCache:imageCache cacheType:targetStoreCacheType waitStoreCache:waitStoreCache completion:^{
- // Continue transform process
- [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:cacheType finished:finished completed:completedBlock];
- }];
- }
- });
- } else {
- [self storeImage:fullSizeImage imageData:downloadedData forKey:key imageCache:imageCache cacheType:targetStoreCacheType waitStoreCache:waitStoreCache completion:^{
- // Continue transform process
- [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:cacheType finished:finished completed:completedBlock];
- }];
- }
- } else {
- // Continue transform process
- [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData 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
- cacheType:(SDImageCacheType)cacheType
- finished:(BOOL)finished
- completed:(nullable SDInternalCompletionBlock)completedBlock {
- // the target image store cache type
- SDImageCacheType storeCacheType = SDImageCacheTypeAll;
- if (context[SDWebImageContextStoreCacheType]) {
- storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
- }
- id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
- if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
- transformer = nil;
- }
-
- // transformer check
- BOOL shouldTransformImage = originalImage && transformer;
- shouldTransformImage = shouldTransformImage && (!originalImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
- shouldTransformImage = shouldTransformImage && (!originalImage.sd_isVector || (options & SDWebImageTransformVectorImage));
-
- // thumbnail check
- // This exist when previous thumbnail pipeline callback into next full size pipeline, because we share the same URL download but need different image
- // Actually this is a hack, we attach the metadata into image object, which should design a better concept like `ImageInfo` and keep that around
- // Redecode need the full size data (progressive decoding or third-party loaders may callback nil data)
- BOOL shouldRedecodeFullImage = originalData && cacheType == SDImageCacheTypeNone;
- if (shouldRedecodeFullImage) {
- // If the retuened image decode options exist (some loaders impl does not use `SDImageLoaderDecode`) but does not match the options we provide, redecode
- SDImageCoderOptions *returnedDecodeOptions = originalImage.sd_decodeOptions;
- if (returnedDecodeOptions) {
- SDImageCoderOptions *decodeOptions = SDGetDecodeOptionsFromContext(context, options, url.absoluteString);
- shouldRedecodeFullImage = ![returnedDecodeOptions isEqualToDictionary:decodeOptions];
- } else {
- shouldRedecodeFullImage = NO;
- }
- }
-
- if (shouldTransformImage) {
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
- @autoreleasepool {
- // transformed/thumbnailed cache key
- NSString *key = [self cacheKeyForURL:url context:context];
- // Case that transformer one thumbnail, which this time need full pixel image
- UIImage *fullSizeImage = originalImage;
- BOOL imageWasRedecoded = NO;
- if (shouldRedecodeFullImage) {
- fullSizeImage = SDImageCacheDecodeImageData(originalData, key, options, context);
- if (fullSizeImage) {
- imageWasRedecoded = YES;
- } else {
- imageWasRedecoded = NO;
- fullSizeImage = originalImage; // Fallback
- }
- }
- UIImage *transformedImage = [transformer transformedImageWithImage:fullSizeImage forKey:key];
- if (transformedImage && finished) {
- BOOL imageWasTransformed = ![transformedImage isEqual:fullSizeImage];
- // Continue store transform cache process
- [self callStoreTransformCacheProcessForOperation:operation url:url options:options context:context image:transformedImage data:originalData cacheType:cacheType finished:finished transformed:imageWasTransformed || imageWasRedecoded completed:completedBlock];
- } else {
- // Continue store transform cache process
- [self callStoreTransformCacheProcessForOperation:operation url:url options:options context:context image:fullSizeImage data:originalData cacheType:cacheType finished:finished transformed:imageWasRedecoded completed:completedBlock];
- }
- }
- });
- } else if (shouldRedecodeFullImage) {
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
- @autoreleasepool {
- // Re-decode because the returned image does not match current request pipeline's context
- UIImage *fullSizeImage = SDImageCacheDecodeImageData(originalData, url.absoluteString, options, context);
- BOOL imageWasRedecoded = NO;
- if (fullSizeImage) {
- imageWasRedecoded = YES;
- } else {
- imageWasRedecoded = NO;
- fullSizeImage = originalImage; // Fallback
- }
- // Continue store transform cache process
- [self callStoreTransformCacheProcessForOperation:operation url:url options:options context:context image:fullSizeImage data:originalData cacheType:cacheType finished:finished transformed:imageWasRedecoded completed:completedBlock];
- }
- });
- } else {
- // Continue store transform cache process
- [self callStoreTransformCacheProcessForOperation:operation url:url options:options context:context image:originalImage data:originalData cacheType:cacheType finished:finished transformed:NO completed:completedBlock];
- }
- }
- - (void)callStoreTransformCacheProcessForOperation:(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
- transformed:(BOOL)transformed
- completed:(nullable SDInternalCompletionBlock)completedBlock {
- // Grab the image cache to use
- id<SDImageCache> imageCache;
- if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
- imageCache = context[SDWebImageContextImageCache];
- } else {
- imageCache = self.imageCache;
- }
- BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
- // the target image store cache type
- SDImageCacheType storeCacheType = SDImageCacheTypeAll;
- if (context[SDWebImageContextStoreCacheType]) {
- storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
- }
- id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
- // thumbnail check
- BOOL shouldThumbnailImage = context[SDWebImageContextImageThumbnailPixelSize] != nil || image.sd_decodeOptions[SDImageCoderDecodeThumbnailPixelSize] != nil;
-
- // Store the transformed/thumbnail image into the cache
- if (transformed || shouldThumbnailImage) {
- NSData *cacheData;
- // pass nil if the image was transformed/thumbnailed, so we can recalculate the data from the image
- if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
- cacheData = [cacheSerializer cacheDataWithImage:image originalData:nil imageURL:url];
- } else {
- cacheData = nil;
- }
- // transformed/thumbnailed cache key
- NSString *key = [self cacheKeyForURL:url context:context];
- [self storeImage:image imageData:cacheData forKey:key imageCache:imageCache cacheType:storeCacheType waitStoreCache:waitStoreCache completion:^{
- [self callCompletionBlockForOperation:operation completion:completedBlock image:image data:data error:nil cacheType:cacheType finished:finished url:url];
- }];
- } else {
- [self callCompletionBlockForOperation:operation completion:completedBlock image:image data:data error:nil cacheType:cacheType finished:finished url:url];
- }
- }
- #pragma mark - Helper
- - (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
- if (!operation) {
- return;
- }
- SD_LOCK(_runningOperationsLock);
- [self.runningOperations removeObject:operation];
- SD_UNLOCK(_runningOperationsLock);
- }
- - (void)storeImage:(nullable UIImage *)image
- imageData:(nullable NSData *)data
- forKey:(nullable NSString *)key
- imageCache:(nonnull id<SDImageCache>)imageCache
- cacheType:(SDImageCacheType)cacheType
- waitStoreCache:(BOOL)waitStoreCache
- completion:(nullable SDWebImageNoParamsBlock)completion {
- // 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 (!waitStoreCache) {
- if (completion) {
- completion();
- }
- }
- }
- - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
- completion:(nullable SDInternalCompletionBlock)completionBlock
- error:(nullable NSError *)error
- url:(nullable NSURL *)url {
- [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url];
- }
- - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
- completion:(nullable SDInternalCompletionBlock)completionBlock
- image:(nullable UIImage *)image
- data:(nullable NSData *)data
- error:(nullable NSError *)error
- cacheType:(SDImageCacheType)cacheType
- finished:(BOOL)finished
- url:(nullable NSURL *)url {
- dispatch_main_async_safe(^{
- if (completionBlock) {
- 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 {
- imageLoader = self.imageLoader;
- }
- // Check whether we should block failed url
- BOOL shouldBlockFailedURL;
- if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) {
- shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error];
- } else {
- 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;
- }
- - (SDWebImageOptionsResult *)processedResultForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
- SDWebImageOptionsResult *result;
- SDWebImageMutableContext *mutableContext = [SDWebImageMutableContext dictionary];
-
- // Image Transformer from manager
- if (!context[SDWebImageContextImageTransformer]) {
- id<SDImageTransformer> transformer = self.transformer;
- [mutableContext setValue:transformer forKey:SDWebImageContextImageTransformer];
- }
- // Cache key filter from manager
- if (!context[SDWebImageContextCacheKeyFilter]) {
- id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;
- [mutableContext setValue:cacheKeyFilter forKey:SDWebImageContextCacheKeyFilter];
- }
- // Cache serializer from manager
- if (!context[SDWebImageContextCacheSerializer]) {
- id<SDWebImageCacheSerializer> cacheSerializer = self.cacheSerializer;
- [mutableContext setValue:cacheSerializer forKey:SDWebImageContextCacheSerializer];
- }
-
- if (mutableContext.count > 0) {
- if (context) {
- [mutableContext addEntriesFromDictionary:context];
- }
- context = [mutableContext copy];
- }
-
- // Apply options processor
- if (self.optionsProcessor) {
- result = [self.optionsProcessor processedResultForURL:url options:options context:context];
- }
- if (!result) {
- // Use default options result
- result = [[SDWebImageOptionsResult alloc] initWithOptions:options context:context];
- }
-
- return result;
- }
- @end
- @implementation SDWebImageCombinedOperation
- - (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];
- }
- }
- @end
|