From f0388739b6421f653d5adc20bb8f9818821c0633 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Wed, 4 Mar 2020 11:37:43 +0800 Subject: [PATCH] Add the new context option, including the cache, loader and coder. They can be used to use custom cache/loader in a more convenient way, instead of creating dummy SDWebImageManager --- SDWebImage/Core/SDImageCacheDefine.m | 10 ++++- SDWebImage/Core/SDImageLoader.m | 29 ++++++++++---- SDWebImage/Core/SDWebImageDefine.h | 20 ++++++++++ SDWebImage/Core/SDWebImageDefine.m | 3 ++ SDWebImage/Core/SDWebImageManager.m | 57 +++++++++++++++++++++------- 5 files changed, 98 insertions(+), 21 deletions(-) diff --git a/SDWebImage/Core/SDImageCacheDefine.m b/SDWebImage/Core/SDImageCacheDefine.m index 75dfb4e6..19db161a 100644 --- a/SDWebImage/Core/SDImageCacheDefine.m +++ b/SDWebImage/Core/SDImageCacheDefine.m @@ -38,6 +38,14 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS mutableCoderOptions[SDImageCoderWebImageContext] = context; SDImageCoderOptions *coderOptions = [mutableCoderOptions copy]; + // Grab the image coder + id imageCoder; + if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) { + imageCoder = context[SDWebImageContextImageCoder]; + } else { + imageCoder = [SDImageCodersManager sharedManager]; + } + if (!decodeFirstFrame) { Class animatedImageClass = context[SDWebImageContextAnimatedImageClass]; // check whether we should use `SDAnimatedImage` @@ -57,7 +65,7 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS } } if (!image) { - image = [[SDImageCodersManager sharedManager] decodedImageWithData:imageData options:coderOptions]; + image = [imageCoder decodedImageWithData:imageData options:coderOptions]; } if (image) { BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage); diff --git a/SDWebImage/Core/SDImageLoader.m b/SDWebImage/Core/SDImageLoader.m index 4c831c59..c529954e 100644 --- a/SDWebImage/Core/SDImageLoader.m +++ b/SDWebImage/Core/SDImageLoader.m @@ -52,6 +52,14 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS mutableCoderOptions[SDImageCoderWebImageContext] = context; SDImageCoderOptions *coderOptions = [mutableCoderOptions copy]; + // Grab the image coder + id imageCoder; + if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) { + imageCoder = context[SDWebImageContextImageCoder]; + } else { + imageCoder = [SDImageCodersManager sharedManager]; + } + if (!decodeFirstFrame) { // check whether we should use `SDAnimatedImage` Class animatedImageClass = context[SDWebImageContextAnimatedImageClass]; @@ -71,7 +79,7 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS } } if (!image) { - image = [[SDImageCodersManager sharedManager] decodedImageWithData:imageData options:coderOptions]; + image = [imageCoder decodedImageWithData:imageData options:coderOptions]; } if (image) { BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage); @@ -127,14 +135,21 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im mutableCoderOptions[SDImageCoderWebImageContext] = context; SDImageCoderOptions *coderOptions = [mutableCoderOptions copy]; + // Grab the progressive image coder id progressiveCoder = objc_getAssociatedObject(operation, SDImageLoaderProgressiveCoderKey); if (!progressiveCoder) { - // We need to create a new instance for progressive decoding to avoid conflicts - for (idcoder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) { - if ([coder conformsToProtocol:@protocol(SDProgressiveImageCoder)] && - [((id)coder) canIncrementalDecodeFromData:imageData]) { - progressiveCoder = [[[coder class] alloc] initIncrementalWithOptions:coderOptions]; - break; + id imageCoder = context[SDWebImageContextImageCoder]; + // Check the progressive coder if provided + if ([imageCoder conformsToProtocol:@protocol(SDProgressiveImageCoder)]) { + progressiveCoder = [[[imageCoder class] alloc] initIncrementalWithOptions:coderOptions]; + } else { + // We need to create a new instance for progressive decoding to avoid conflicts + for (id coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) { + if ([coder conformsToProtocol:@protocol(SDProgressiveImageCoder)] && + [((id)coder) canIncrementalDecodeFromData:imageData]) { + progressiveCoder = [[[coder class] alloc] initIncrementalWithOptions:coderOptions]; + break; + } } } objc_setAssociatedObject(operation, SDImageLoaderProgressiveCoderKey, progressiveCoder, OBJC_ASSOCIATION_RETAIN_NONATOMIC); diff --git a/SDWebImage/Core/SDWebImageDefine.h b/SDWebImage/Core/SDWebImageDefine.h index 568de147..4ad3aca9 100644 --- a/SDWebImage/Core/SDWebImageDefine.h +++ b/SDWebImage/Core/SDWebImageDefine.h @@ -213,9 +213,29 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextSetIma /** A SDWebImageManager instance to control the image download and cache process using in UIImageView+WebCache category and likes. If not provided, use the shared manager (SDWebImageManager *) + @note Consider deprecated. This context options can be replaced by other context option control like `.imageCache`, `.imageLoader`, `.imageTransofmer` (See below), which already matches all the properties in SDWebImageManager. */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCustomManager; +/** + A id instance which conforms to `SDImageCache` protocol. It's used to override the image mananger's cache during the image loading pipeline. + In other word, if you just want to specify a custom cache during image loading, you don't need to re-create a dummy SDWebImageManager instance with the cache. If not provided, use the image manager's cache (id) + */ +FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageCache; + +/** + A id instance which conforms to `SDImageLoader` protocol. It's used to override the image mananger's loader during the image loading pipeline. + In other word, if you just want to specify a custom loader during image loading, you don't need to re-create a dummy SDWebImageManager instance with the loader. If not provided, use the image manager's cache (id) +*/ +FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageLoader; + +/** + A id instance which conforms to `SDImageCoder` protocol. It's used to override the default image codre for image decoding(including progressive) and encoding during the image loading process. + If you use this context option, we will not always use `SDImageCodersManager.shared` to loop through all registered coders and find the suitable one. Instead, we will arbitrarily use the exact provided coder without extra checking (We may not call `canDecodeFromData:`). + @note This is only useful for cases which you can ensure the loading url matches your coder, or you find it's too hard to write a common coder which can used for generic usage. This will bind the loading url with the coder logic, which is not always a good design, but possible. (id) +*/ +FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageCoder; + /** A id instance which conforms `SDImageTransformer` protocol. It's used for image transform after the image load finished and store the transformed image to cache. If you provide one, it will ignore the `transformer` in manager and use provided one instead. (id) */ diff --git a/SDWebImage/Core/SDWebImageDefine.m b/SDWebImage/Core/SDWebImageDefine.m index 921e878a..866b164d 100644 --- a/SDWebImage/Core/SDWebImageDefine.m +++ b/SDWebImage/Core/SDWebImageDefine.m @@ -120,6 +120,9 @@ inline UIImage * _Nullable SDScaledImageForScaleFactor(CGFloat scale, UIImage * SDWebImageContextOption const SDWebImageContextSetImageOperationKey = @"setImageOperationKey"; SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager"; +SDWebImageContextOption const SDWebImageContextImageCache = @"imageCache"; +SDWebImageContextOption const SDWebImageContextImageLoader = @"imageLoader"; +SDWebImageContextOption const SDWebImageContextImageCoder = @"imageCoder"; SDWebImageContextOption const SDWebImageContextImageTransformer = @"imageTransformer"; SDWebImageContextOption const SDWebImageContextImageScaleFactor = @"imageScaleFactor"; SDWebImageContextOption const SDWebImageContextImagePreserveAspectRatio = @"imagePreserveAspectRatio"; diff --git a/SDWebImage/Core/SDWebImageManager.m b/SDWebImage/Core/SDWebImageManager.m index c5502cc3..c4aff6ac 100644 --- a/SDWebImage/Core/SDWebImageManager.m +++ b/SDWebImage/Core/SDWebImageManager.m @@ -211,12 +211,19 @@ static id _defaultImageLoader; context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock { + // Grab the image cache to use + id imageCache; + if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { + imageCache = context[SDWebImageContextImageCache]; + } else { + imageCache = self.imageCache; + } // 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 = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) { + operation.cacheOperation = [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 @@ -243,11 +250,18 @@ static id _defaultImageLoader; cacheType:(SDImageCacheType)cacheType progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock { + // Grab the image loader to use + id 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]); - shouldDownload &= [self.imageLoader canRequestImageForURL:url]; + 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 @@ -265,7 +279,7 @@ static id _defaultImageLoader; } @weakify(operation); - operation.loaderOperation = [self.imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { + 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 @@ -277,7 +291,7 @@ static id _defaultImageLoader; [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]; + BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context]; if (shouldBlockFailedURL) { SD_LOCK(self.failedURLsLock); @@ -336,7 +350,6 @@ static id _defaultImageLoader; shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)); shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isVector || (options & SDWebImageTransformVectorImage)); BOOL shouldCacheOriginal = downloadedImage && finished; - BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache); // if available, store original image to cache if (shouldCacheOriginal) { @@ -346,14 +359,14 @@ static id _defaultImageLoader; 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 waitStoreCache:waitStoreCache completion:^{ + [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 waitStoreCache:waitStoreCache completion:^{ + [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]; }]; @@ -385,7 +398,6 @@ static id _defaultImageLoader; BOOL shouldTransformImage = originalImage && transformer; shouldTransformImage = shouldTransformImage && (!originalImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)); shouldTransformImage = shouldTransformImage && (!originalImage.sd_isVector || (options & SDWebImageTransformVectorImage)); - BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache); // if available, store transformed image to cache if (shouldTransformImage) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ @@ -404,7 +416,7 @@ static id _defaultImageLoader; } // keep the original image format and extended data SDImageCopyAssociatedObject(originalImage, transformedImage); - [self storeImage:transformedImage imageData:cacheData forKey:cacheKey cacheType:storeCacheType waitStoreCache:waitStoreCache completion:^{ + [self storeImage:transformedImage imageData:cacheData forKey:cacheKey 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 { @@ -432,10 +444,18 @@ static id _defaultImageLoader; imageData:(nullable NSData *)data forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType - waitStoreCache:(BOOL)waitStoreCache + options:(SDWebImageOptions)options + context:(nullable SDWebImageContext *)context completion:(nullable SDWebImageNoParamsBlock)completion { + id imageCache; + if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { + imageCache = context[SDWebImageContextImageCache]; + } else { + imageCache = self.imageCache; + } + BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache); // Check whether we should wait the store cache finished. If not, callback immediately - [self.imageCache storeImage:image imageData:data forKey:key cacheType:cacheType completion:^{ + [imageCache storeImage:image imageData:data forKey:key cacheType:cacheType completion:^{ if (waitStoreCache) { if (completion) { completion(); @@ -472,13 +492,24 @@ static id _defaultImageLoader; } - (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url - error:(nonnull NSError *)error { + error:(nonnull NSError *)error + options:(SDWebImageOptions)options + context:(nullable SDWebImageContext *)context { + if ((options & SDWebImageRetryFailed)) { + return NO; + } + id 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 { - shouldBlockFailedURL = [self.imageLoader shouldBlockFailedURLWithURL:url error:error]; + shouldBlockFailedURL = [imageLoader shouldBlockFailedURLWithURL:url error:error]; } return shouldBlockFailedURL;