diff --git a/SDWebImage/Core/SDImageCache.h b/SDWebImage/Core/SDImageCache.h index 1b1afd47..ec90efb0 100644 --- a/SDWebImage/Core/SDImageCache.h +++ b/SDWebImage/Core/SDImageCache.h @@ -235,6 +235,15 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { */ - (nullable NSData *)diskImageDataForKey:(nullable NSString *)key; +/** + * Asynchronously load the image data in disk cache. You can decode the image data to image after loaded. + * + * @param key The unique key used to store the wanted image + * @param completionBlock the block to be executed when the check is done. + * @note the completion block will be always executed on the main queue + */ +- (void)diskImageDataQueryForKey:(nullable NSString *)key completion:(nullable SDImageCacheQueryDataCompletionBlock)completionBlock; + /** * Operation that queries the cache asynchronously and call the completion when done. * @@ -268,6 +277,19 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { */ - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock; +/** + * Asynchronously queries the cache with operation and call the completion when done. + * + * @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`. + * @param options A mask to specify options to use for this cache query + * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. + * @param queryCacheType Specify where to query the cache from. By default we use `.all`, which means both memory cache and disk cache. You can choose to query memory only or disk only as well. Pass `.none` is invalid and callback with nil immediatelly. + * @param doneBlock The completion block. Will not get called if the operation is cancelled + * + * @return a NSOperation instance containing the cache op + */ +- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock; + /** * Synchronously query the memory cache. * diff --git a/SDWebImage/Core/SDImageCache.m b/SDWebImage/Core/SDImageCache.m index f7585895..af201386 100644 --- a/SDWebImage/Core/SDImageCache.m +++ b/SDWebImage/Core/SDImageCache.m @@ -314,6 +314,17 @@ return [self.diskCache containsDataForKey:key]; } +- (void)diskImageDataQueryForKey:(NSString *)key completion:(SDImageCacheQueryDataCompletionBlock)completionBlock { + dispatch_async(self.ioQueue, ^{ + NSData *imageData = [self diskImageDataBySearchingAllPathsForKey:key]; + if (completionBlock) { + dispatch_async(dispatch_get_main_queue(), ^{ + completionBlock(imageData); + }); + } + }); +} + - (nullable NSData *)diskImageDataForKey:(nullable NSString *)key { if (!key) { return nil; @@ -426,12 +437,23 @@ } - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock { + return [self queryCacheOperationForKey:key options:options context:context cacheType:SDImageCacheTypeAll done:doneBlock]; +} + +- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock { if (!key) { if (doneBlock) { doneBlock(nil, nil, SDImageCacheTypeNone); } return nil; } + // Invalid cache type + if (queryCacheType == SDImageCacheTypeNone) { + if (doneBlock) { + doneBlock(nil, nil, SDImageCacheTypeNone); + } + return nil; + } id transformer = context[SDWebImageContextImageTransformer]; if (transformer) { @@ -441,7 +463,10 @@ } // First check the in-memory cache... - UIImage *image = [self imageFromMemoryCacheForKey:key]; + UIImage *image; + if (queryCacheType != SDImageCacheTypeDisk) { + image = [self imageFromMemoryCacheForKey:key]; + } if (image) { if (options & SDImageCacheDecodeFirstFrameOnly) { @@ -464,7 +489,7 @@ } } - BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryMemoryData)); + BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData)); if (shouldQueryMemoryOnly) { if (doneBlock) { doneBlock(image, nil, SDImageCacheTypeMemory); @@ -699,6 +724,10 @@ #pragma mark - SDImageCache - (id)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock { + return [self queryImageForKey:key options:options context:context cacheType:SDImageCacheTypeAll completion:completionBlock]; +} + +- (id)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock { SDImageCacheOptions cacheOptions = 0; if (options & SDWebImageQueryMemoryData) cacheOptions |= SDImageCacheQueryMemoryData; if (options & SDWebImageQueryMemoryDataSync) cacheOptions |= SDImageCacheQueryMemoryDataSync; @@ -709,7 +738,7 @@ if (options & SDWebImagePreloadAllFrames) cacheOptions |= SDImageCachePreloadAllFrames; if (options & SDWebImageMatchAnimatedImageClass) cacheOptions |= SDImageCacheMatchAnimatedImageClass; - return [self queryCacheOperationForKey:key options:cacheOptions context:context done:completionBlock]; + return [self queryCacheOperationForKey:key options:cacheOptions context:context cacheType:cacheType done:completionBlock]; } - (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock { diff --git a/SDWebImage/Core/SDImageCacheDefine.h b/SDWebImage/Core/SDImageCacheDefine.h index be4e0211..e2449bfd 100644 --- a/SDWebImage/Core/SDImageCacheDefine.h +++ b/SDWebImage/Core/SDImageCacheDefine.h @@ -36,6 +36,7 @@ typedef NS_ENUM(NSInteger, SDImageCacheType) { }; typedef void(^SDImageCacheCheckCompletionBlock)(BOOL isInCache); +typedef void(^SDImageCacheQueryDataCompletionBlock)(NSData * _Nullable data); typedef void(^SDImageCacheCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize); typedef NSString * _Nullable (^SDImageCacheAdditionalCachePathBlock)(NSString * _Nonnull key); typedef void(^SDImageCacheQueryCompletionBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType); @@ -76,6 +77,23 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonn context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock; +/** + Query the cached image from image cache for given key. The operation can be used to cancel the query. + If image is cached in memory, completion is called synchronously, else aynchronously and depends on the options arg (See `SDWebImageQueryDiskSync`) + + @param key The image cache key + @param options A mask to specify options to use for this query + @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. + @param cacheType Specify where to query the cache from. By default we use `.all`, which means both memory cache and disk cache. You can choose to query memory only or disk only as well. Pass `.none` is invalid and callback with nil immediatelly. + @param completionBlock The completion block. Will not get called if the operation is cancelled + @return The operation for this query + */ +- (nullable id)queryImageForKey:(nullable NSString *)key + options:(SDWebImageOptions)options + context:(nullable SDWebImageContext *)context + cacheType:(SDImageCacheType)cacheType + completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock; + /** Store the image into image cache for the given key. If cache type is memory only, completion is called synchronously, else aynchronously. diff --git a/SDWebImage/Core/SDImageCachesManager.m b/SDWebImage/Core/SDImageCachesManager.m index 6b6f7d8a..b6b13c12 100644 --- a/SDWebImage/Core/SDImageCachesManager.m +++ b/SDWebImage/Core/SDImageCachesManager.m @@ -85,6 +85,10 @@ #pragma mark - SDImageCache - (id)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context completion:(SDImageCacheQueryCompletionBlock)completionBlock { + return [self queryImageForKey:key options:options context:context cacheType:SDImageCacheTypeAll completion:completionBlock]; +} + +- (id)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock { if (!key) { return nil; } @@ -93,30 +97,30 @@ if (count == 0) { return nil; } else if (count == 1) { - return [caches.firstObject queryImageForKey:key options:options context:context completion:completionBlock]; + return [caches.firstObject queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock]; } switch (self.queryOperationPolicy) { case SDImageCachesManagerOperationPolicyHighestOnly: { id cache = caches.lastObject; - return [cache queryImageForKey:key options:options context:context completion:completionBlock]; + return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock]; } break; case SDImageCachesManagerOperationPolicyLowestOnly: { id cache = caches.firstObject; - return [cache queryImageForKey:key options:options context:context completion:completionBlock]; + return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock]; } break; case SDImageCachesManagerOperationPolicyConcurrent: { SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new]; [operation beginWithTotalCount:caches.count]; - [self concurrentQueryImageForKey:key options:options context:context completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation]; + [self concurrentQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation]; return operation; } break; case SDImageCachesManagerOperationPolicySerial: { SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new]; [operation beginWithTotalCount:caches.count]; - [self serialQueryImageForKey:key options:options context:context completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation]; + [self serialQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation]; return operation; } break; @@ -279,11 +283,11 @@ #pragma mark - Concurrent Operation -- (void)concurrentQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation { +- (void)concurrentQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation { NSParameterAssert(enumerator); NSParameterAssert(operation); for (id cache in enumerator) { - [cache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { + [cache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { if (operation.isCancelled) { // Cancelled return; @@ -422,7 +426,7 @@ #pragma mark - Serial Operation -- (void)serialQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation { +- (void)serialQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation { NSParameterAssert(enumerator); NSParameterAssert(operation); id cache = enumerator.nextObject; @@ -435,7 +439,7 @@ return; } @weakify(self); - [cache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { + [cache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { @strongify(self); if (operation.isCancelled) { // Cancelled @@ -455,7 +459,7 @@ return; } // Next - [self serialQueryImageForKey:key options:options context:context completion:completionBlock enumerator:enumerator operation:operation]; + [self serialQueryImageForKey:key options:options context:context cacheType:queryCacheType completion:completionBlock enumerator:enumerator operation:operation]; }]; } diff --git a/SDWebImage/Core/SDWebImageDefine.h b/SDWebImage/Core/SDWebImageDefine.h index 1be59810..6f02f279 100644 --- a/SDWebImage/Core/SDWebImageDefine.h +++ b/SDWebImage/Core/SDWebImageDefine.h @@ -259,6 +259,12 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageP */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageThumbnailPixelSize; +/** + A SDImageCacheType raw value which specify the source of cache to query. Specify `SDImageCacheTypeDisk` to query from disk cache only; `SDImageCacheTypeMemory` to query from memory only. And `SDImageCacheTypeAll` to query from both memory cache and disk cache. Specify `SDImageCacheTypeNone` is invalid and totally ignore the cache query. + If not provide or the value is invalid, we will use `SDImageCacheTypeAll`. (NSNumber) + */ +FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextQueryCacheType; + /** A SDImageCacheType raw value which specify the store cache type when the image has just been downloaded and will be stored to the cache. Specify `SDImageCacheTypeNone` to disable cache storage; `SDImageCacheTypeDisk` to store in disk cache only; `SDImageCacheTypeMemory` to store in memory only. And `SDImageCacheTypeAll` to store in both memory cache and disk cache. If you use image transformer feature, this actually apply for the transformed image, but not the original image itself. Use `SDWebImageContextOriginalStoreCacheType` if you want to control the original image's store cache type at the same time. diff --git a/SDWebImage/Core/SDWebImageDefine.m b/SDWebImage/Core/SDWebImageDefine.m index 866b164d..2809af69 100644 --- a/SDWebImage/Core/SDWebImageDefine.m +++ b/SDWebImage/Core/SDWebImageDefine.m @@ -127,6 +127,7 @@ SDWebImageContextOption const SDWebImageContextImageTransformer = @"imageTransfo SDWebImageContextOption const SDWebImageContextImageScaleFactor = @"imageScaleFactor"; SDWebImageContextOption const SDWebImageContextImagePreserveAspectRatio = @"imagePreserveAspectRatio"; SDWebImageContextOption const SDWebImageContextImageThumbnailPixelSize = @"imageThumbnailPixelSize"; +SDWebImageContextOption const SDWebImageContextQueryCacheType = @"queryCacheType"; SDWebImageContextOption const SDWebImageContextStoreCacheType = @"storeCacheType"; SDWebImageContextOption const SDWebImageContextOriginalStoreCacheType = @"originalStoreCacheType"; SDWebImageContextOption const SDWebImageContextAnimatedImageClass = @"animatedImageClass"; diff --git a/SDWebImage/Core/SDWebImageManager.m b/SDWebImage/Core/SDWebImageManager.m index 4d33e6d7..a58831aa 100644 --- a/SDWebImage/Core/SDWebImageManager.m +++ b/SDWebImage/Core/SDWebImageManager.m @@ -220,10 +220,15 @@ static id _defaultImageLoader; } // Check whether we should query cache BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly); + // Get the query cache type + SDImageCacheType queryCacheType = SDImageCacheTypeAll; + if (context[SDWebImageContextQueryCacheType]) { + queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue]; + } if (shouldQueryCache) { NSString *key = [self cacheKeyForURL:url context:context]; @weakify(operation); - operation.cacheOperation = [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 cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) { @strongify(operation); if (!operation || operation.isCancelled) { // Image combined operation cancelled by user diff --git a/Tests/Tests/SDImageCacheTests.m b/Tests/Tests/SDImageCacheTests.m index 93adcd47..96fb0f48 100644 --- a/Tests/Tests/SDImageCacheTests.m +++ b/Tests/Tests/SDImageCacheTests.m @@ -610,7 +610,7 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png"; - (void)test50SDImageCacheQueryOp { XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache query op works"]; [[SDImageCache sharedImageCache] storeImage:[self testJPEGImage] forKey:kTestImageKeyJPEG toDisk:NO completion:nil]; - [[SDImageCachesManager sharedManager] queryImageForKey:kTestImageKeyJPEG options:0 context:nil completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { + [[SDImageCachesManager sharedManager] queryImageForKey:kTestImageKeyJPEG options:0 context:nil cacheType:SDImageCacheTypeAll completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { expect(image).notTo.beNil(); [expectation fulfill]; }]; @@ -680,7 +680,7 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png"; cachesManager.removeOperationPolicy = SDImageCachesManagerOperationPolicyLowestOnly; cachesManager.containsOperationPolicy = SDImageCachesManagerOperationPolicyLowestOnly; cachesManager.clearOperationPolicy = SDImageCachesManagerOperationPolicyLowestOnly; - [cachesManager queryImageForKey:kTestImageKeyJPEG options:0 context:nil completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { + [cachesManager queryImageForKey:kTestImageKeyJPEG options:0 context:nil cacheType:SDImageCacheTypeAll completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { expect(image).to.beNil(); }]; [cachesManager storeImage:[self testJPEGImage] imageData:nil forKey:kTestImageKeyJPEG cacheType:SDImageCacheTypeMemory completion:nil]; @@ -699,7 +699,7 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png"; cachesManager.removeOperationPolicy = SDImageCachesManagerOperationPolicyHighestOnly; cachesManager.containsOperationPolicy = SDImageCachesManagerOperationPolicyHighestOnly; cachesManager.clearOperationPolicy = SDImageCachesManagerOperationPolicyHighestOnly; - [cachesManager queryImageForKey:kTestImageKeyPNG options:0 context:nil completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { + [cachesManager queryImageForKey:kTestImageKeyPNG options:0 context:nil cacheType:SDImageCacheTypeAll completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { expect(image).to.beNil(); }]; [cachesManager storeImage:[self testPNGImage] imageData:nil forKey:kTestImageKeyPNG cacheType:SDImageCacheTypeMemory completion:nil]; @@ -732,7 +732,7 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png"; cachesManager.removeOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent; cachesManager.containsOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent; cachesManager.clearOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent; - [cachesManager queryImageForKey:kConcurrentTestImageKey options:0 context:nil completion:nil]; + [cachesManager queryImageForKey:kConcurrentTestImageKey options:0 context:nil cacheType:SDImageCacheTypeAll completion:nil]; [cachesManager storeImage:[self testJPEGImage] imageData:nil forKey:kConcurrentTestImageKey cacheType:SDImageCacheTypeMemory completion:nil]; [cachesManager removeImageForKey:kConcurrentTestImageKey cacheType:SDImageCacheTypeMemory completion:nil]; [cachesManager clearWithCacheType:SDImageCacheTypeMemory completion:nil]; @@ -772,7 +772,7 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png"; cachesManager.removeOperationPolicy = SDImageCachesManagerOperationPolicySerial; cachesManager.containsOperationPolicy = SDImageCachesManagerOperationPolicySerial; cachesManager.clearOperationPolicy = SDImageCachesManagerOperationPolicySerial; - [cachesManager queryImageForKey:kSerialTestImageKey options:0 context:nil completion:nil]; + [cachesManager queryImageForKey:kSerialTestImageKey options:0 context:nil cacheType:SDImageCacheTypeAll completion:nil]; [cachesManager storeImage:[self testJPEGImage] imageData:nil forKey:kSerialTestImageKey cacheType:SDImageCacheTypeMemory completion:nil]; [cachesManager removeImageForKey:kSerialTestImageKey cacheType:SDImageCacheTypeMemory completion:nil]; [cachesManager clearWithCacheType:SDImageCacheTypeMemory completion:nil]; @@ -793,6 +793,41 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png"; [self waitForExpectationsWithCommonTimeout]; } +- (void)test58CustomImageCache { + NSString *cachePath = [[self userCacheDirectory] stringByAppendingPathComponent:@"custom"]; + SDImageCacheConfig *config = [[SDImageCacheConfig alloc] init]; + SDWebImageTestCache *cache = [[SDWebImageTestCache alloc] initWithCachePath:cachePath config:config]; + expect(cache.memoryCache).notTo.beNil(); + expect(cache.diskCache).notTo.beNil(); + + // Clear + [cache clearWithCacheType:SDImageCacheTypeAll completion:nil]; + // Store + UIImage *image1 = self.testJPEGImage; + NSString *key1 = @"testJPEGImage"; + [cache storeImage:image1 imageData:nil forKey:key1 cacheType:SDImageCacheTypeAll completion:nil]; + // Contain + [cache containsImageForKey:key1 cacheType:SDImageCacheTypeAll completion:^(SDImageCacheType containsCacheType) { + expect(containsCacheType).equal(SDImageCacheTypeMemory); + }]; + // Query + [cache queryImageForKey:key1 options:0 context:nil cacheType:SDImageCacheTypeAll completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { + expect(image).equal(image1); + expect(data).beNil(); + expect(cacheType).equal(SDImageCacheTypeMemory); + }]; + // Remove + [cache removeImageForKey:key1 cacheType:SDImageCacheTypeAll completion:nil]; + // Contain + [cache containsImageForKey:key1 cacheType:SDImageCacheTypeAll completion:^(SDImageCacheType containsCacheType) { + expect(containsCacheType).equal(SDImageCacheTypeNone); + }]; + // Clear + [cache clearWithCacheType:SDImageCacheTypeAll completion:nil]; + NSArray *cacheFiles = [cache.diskCache.fileManager contentsOfDirectoryAtPath:cachePath error:nil]; + expect(cacheFiles.count).equal(0); +} + #pragma mark Helper methods - (UIImage *)testJPEGImage { diff --git a/Tests/Tests/SDWebImageDownloaderTests.m b/Tests/Tests/SDWebImageDownloaderTests.m index da0ee89b..4cd877c1 100644 --- a/Tests/Tests/SDWebImageDownloaderTests.m +++ b/Tests/Tests/SDWebImageDownloaderTests.m @@ -474,7 +474,6 @@ [self waitForExpectationsWithCommonTimeout]; } -#if SD_UIKIT - (void)test22ThatCustomDecoderWorksForImageDownload { XCTestExpectation *expectation = [self expectationWithDescription:@"Custom decoder for SDWebImageDownloader not works"]; SDWebImageDownloader *downloader = [[SDWebImageDownloader alloc] init]; @@ -487,8 +486,8 @@ UIImage *testJPEGImage = [[UIImage alloc] initWithContentsOfFile:testJPEGImagePath]; [downloader downloadImageWithURL:testImageURL options:0 progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) { - NSData *data1 = UIImagePNGRepresentation(testJPEGImage); - NSData *data2 = UIImagePNGRepresentation(image); + NSData *data1 = [testJPEGImage sd_imageDataAsFormat:SDImageFormatPNG]; + NSData *data2 = [image sd_imageDataAsFormat:SDImageFormatPNG]; if (![data1 isEqualToData:data2]) { XCTFail(@"The image data is not equal to cutom decoder, check -[SDWebImageTestDecoder decodedImageWithData:]"); } @@ -499,7 +498,6 @@ [self waitForExpectationsWithCommonTimeout]; [downloader invalidateSessionAndCancel:YES]; } -#endif - (void)test23ThatDownloadRequestModifierWorks { XCTestExpectation *expectation = [self expectationWithDescription:@"Download request modifier not works"]; diff --git a/Tests/Tests/SDWebImageManagerTests.m b/Tests/Tests/SDWebImageManagerTests.m index c75fc950..305caf3c 100644 --- a/Tests/Tests/SDWebImageManagerTests.m +++ b/Tests/Tests/SDWebImageManagerTests.m @@ -8,6 +8,8 @@ #import "SDTestCase.h" #import "SDWebImageTestTransformer.h" +#import "SDWebImageTestCache.h" +#import "SDWebImageTestLoader.h" @interface SDWebImageManagerTests : SDTestCase @@ -268,6 +270,53 @@ }]; } +- (void)test14ThatCustomCacheAndLoaderWorks { + XCTestExpectation *expectation = [self expectationWithDescription:@"Custom Cache and Loader during manger query"]; + NSURL *url = [NSURL URLWithString:@"http://via.placeholder.com/100x100.png"]; + SDWebImageContext *context = @{ + SDWebImageContextImageCache : SDWebImageTestCache.sharedCache, + SDWebImageContextImageLoader : SDWebImageTestLoader.sharedLoader + }; + [SDWebImageTestCache.sharedCache clearWithCacheType:SDImageCacheTypeAll completion:nil]; + [SDWebImageManager.sharedManager loadImageWithURL:url options:SDWebImageWaitStoreCache context:context progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { + expect(image).notTo.beNil(); + expect(image.size.width).equal(100); + expect(image.size.height).equal(100); + expect(data).notTo.beNil(); + NSString *cacheKey = [SDWebImageManager.sharedManager cacheKeyForURL:imageURL]; + // Check Disk Cache (SDWebImageWaitStoreCache behavior) + [SDWebImageTestCache.sharedCache containsImageForKey:cacheKey cacheType:SDImageCacheTypeDisk completion:^(SDImageCacheType containsCacheType) { + expect(containsCacheType).equal(SDImageCacheTypeDisk); + [expectation fulfill]; + }]; + }]; + + [self waitForExpectationsWithCommonTimeout]; +} + +- (void)test15ThatQueryCacheTypeWork { + XCTestExpectation *expectation = [self expectationWithDescription:@"Image query cache type works"]; + NSURL *url = [NSURL URLWithString:@"http://via.placeholder.com/101x101.png"]; + NSString *key = [SDWebImageManager.sharedManager cacheKeyForURL:url]; + NSData *testImageData = [NSData dataWithContentsOfFile:[self testJPEGPath]]; + [SDImageCache.sharedImageCache storeImageDataToDisk:testImageData forKey:key]; + + // Query memory first + [SDWebImageManager.sharedManager loadImageWithURL:url options:SDWebImageFromCacheOnly context:@{SDWebImageContextQueryCacheType : @(SDImageCacheTypeMemory)} progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { + expect(image).beNil(); + expect(cacheType).equal(SDImageCacheTypeNone); + // Query disk secondly + [SDWebImageManager.sharedManager loadImageWithURL:url options:SDWebImageFromCacheOnly context:@{SDWebImageContextQueryCacheType : @(SDImageCacheTypeDisk)} progress:nil completed:^(UIImage * _Nullable image2, NSData * _Nullable data2, NSError * _Nullable error2, SDImageCacheType cacheType2, BOOL finished2, NSURL * _Nullable imageURL2) { + expect(image2).notTo.beNil(); + expect(cacheType2).equal(SDImageCacheTypeDisk); + [SDImageCache.sharedImageCache removeImageFromDiskForKey:key]; + [expectation fulfill]; + }]; + }]; + + [self waitForExpectationsWithCommonTimeout]; +} + - (NSString *)testJPEGPath { NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; return [testBundle pathForResource:@"TestImage" ofType:@"jpg"]; diff --git a/Tests/Tests/SDWebImageTestCache.h b/Tests/Tests/SDWebImageTestCache.h index 0736c698..6c900c6f 100644 --- a/Tests/Tests/SDWebImageTestCache.h +++ b/Tests/Tests/SDWebImageTestCache.h @@ -9,9 +9,9 @@ #import #import +#import // A really naive implementation of custom memory cache and disk cache - @interface SDWebImageTestMemoryCache : NSObject @property (nonatomic, strong, nonnull) SDImageCacheConfig *config; @@ -26,3 +26,16 @@ @property (nonatomic, strong, nonnull) NSFileManager *fileManager; @end + +// A really naive implementation of custom image cache using memory cache and disk cache +@interface SDWebImageTestCache : NSObject + +@property (nonatomic, strong, nonnull) SDImageCacheConfig *config; +@property (nonatomic, strong, nonnull) SDWebImageTestMemoryCache *memoryCache; +@property (nonatomic, strong, nonnull) SDWebImageTestDiskCache *diskCache; + +- (nullable instancetype)initWithCachePath:(nonnull NSString *)cachePath config:(nonnull SDImageCacheConfig *)config; + +@property (nonatomic, class, readonly, nonnull) SDWebImageTestCache *sharedCache; + +@end diff --git a/Tests/Tests/SDWebImageTestCache.m b/Tests/Tests/SDWebImageTestCache.m index 68b2a154..762a2d9a 100644 --- a/Tests/Tests/SDWebImageTestCache.m +++ b/Tests/Tests/SDWebImageTestCache.m @@ -8,7 +8,7 @@ */ #import "SDWebImageTestCache.h" -#import +#import #import "SDFileAttributeHelper.h" static NSString * const SDWebImageTestDiskCacheExtendedAttributeName = @"com.hackemist.SDWebImageTestDiskCache"; @@ -49,7 +49,7 @@ static NSString * const SDWebImageTestDiskCacheExtendedAttributeName = @"com.hac @implementation SDWebImageTestDiskCache - (nullable NSString *)cachePathForKey:(nonnull NSString *)key { - return [self.cachePath stringByAppendingPathComponent:key]; + return [self.cachePath stringByAppendingPathComponent:key.lastPathComponent]; } - (BOOL)containsDataForKey:(nonnull NSString *)key { @@ -72,7 +72,10 @@ static NSString * const SDWebImageTestDiskCacheExtendedAttributeName = @"com.hac } - (void)removeAllData { - [self.fileManager removeItemAtPath:self.cachePath error:nil]; + for (NSString *path in [self.fileManager subpathsAtPath:self.cachePath]) { + NSString *filePath = [self.cachePath stringByAppendingPathComponent:path]; + [self.fileManager removeItemAtPath:filePath error:nil]; + } } - (void)removeDataForKey:(nonnull NSString *)key { @@ -122,3 +125,170 @@ static NSString * const SDWebImageTestDiskCacheExtendedAttributeName = @"com.hac } @end + +@implementation SDWebImageTestCache + ++ (SDWebImageTestCache *)sharedCache { + static dispatch_once_t onceToken; + static SDWebImageTestCache *cache; + dispatch_once(&onceToken, ^{ + NSString *cachePath = [[self userCacheDirectory] stringByAppendingPathComponent:@"SDWebImageTestCache"]; + SDImageCacheConfig *config = SDImageCacheConfig.defaultCacheConfig; + cache = [[SDWebImageTestCache alloc] initWithCachePath:cachePath config:config]; + }); + return cache; +} + +- (instancetype)initWithCachePath:(NSString *)cachePath config:(SDImageCacheConfig *)config { + self = [super init]; + if (self) { + self.config = config; + self.memoryCache = [[SDWebImageTestMemoryCache alloc] initWithConfig:config]; + self.diskCache = [[SDWebImageTestDiskCache alloc] initWithCachePath:cachePath config:config]; + } + return self; +} + +- (void)clearWithCacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock { + switch (cacheType) { + case SDImageCacheTypeNone: + break; + case SDImageCacheTypeMemory: + [self.memoryCache removeAllObjects]; + break; + case SDImageCacheTypeDisk: + [self.diskCache removeAllData]; + break; + case SDImageCacheTypeAll: + [self.memoryCache removeAllObjects]; + [self.diskCache removeAllData]; + break; + default: + break; + } + if (completionBlock) { + completionBlock(); + } +} + +- (void)containsImageForKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheContainsCompletionBlock)completionBlock { + SDImageCacheType containsCacheType = SDImageCacheTypeNone; + switch (cacheType) { + case SDImageCacheTypeNone: + break; + case SDImageCacheTypeMemory: + containsCacheType = [self.memoryCache objectForKey:key] ? SDImageCacheTypeMemory : SDImageCacheTypeNone; + break; + case SDImageCacheTypeDisk: + containsCacheType = [self.diskCache containsDataForKey:key] ? SDImageCacheTypeDisk : SDImageCacheTypeNone; + break; + case SDImageCacheTypeAll: + if ([self.memoryCache objectForKey:key]) { + containsCacheType = SDImageCacheTypeMemory; + } else if ([self.diskCache containsDataForKey:key]) { + containsCacheType = SDImageCacheTypeDisk; + } + break; + default: + break; + } + if (completionBlock) { + completionBlock(containsCacheType); + } +} + +- (nullable id)queryImageForKey:(nullable NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock { + return [self queryImageForKey:key options:options context:context cacheType:SDImageCacheTypeAll completion:completionBlock]; +} + +- (nullable id)queryImageForKey:(nullable NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock { + UIImage *image; + NSData *data; + SDImageCacheType resultCacheType = SDImageCacheTypeNone; + switch (cacheType) { + case SDImageCacheTypeNone: + break; + case SDImageCacheTypeMemory: + image = [self.memoryCache objectForKey:key]; + if (image) { + resultCacheType = SDImageCacheTypeMemory; + } + break; + case SDImageCacheTypeDisk: + data = [self.diskCache dataForKey:key]; + image = [UIImage sd_imageWithData:data]; + if (data) { + resultCacheType = SDImageCacheTypeDisk; + } + break; + case SDImageCacheTypeAll: + image = [self.memoryCache objectForKey:key]; + if (image) { + resultCacheType = SDImageCacheTypeMemory; + } else { + data = [self.diskCache dataForKey:key]; + image = [UIImage sd_imageWithData:data]; + if (data) { + resultCacheType = SDImageCacheTypeDisk; + } + } + break; + default: + break; + } + if (completionBlock) { + completionBlock(image, data, resultCacheType); + } + return nil; +} + +- (void)removeImageForKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock { + switch (cacheType) { + case SDImageCacheTypeNone: + break; + case SDImageCacheTypeMemory: + [self.memoryCache removeObjectForKey:key]; + break; + case SDImageCacheTypeDisk: + [self.diskCache removeDataForKey:key]; + break; + case SDImageCacheTypeAll: + [self.memoryCache removeObjectForKey:key]; + [self.diskCache removeDataForKey:key]; + break; + default: + break; + } + if (completionBlock) { + completionBlock(); + } +} + +- (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock { + switch (cacheType) { + case SDImageCacheTypeNone: + break; + case SDImageCacheTypeMemory: + [self.memoryCache setObject:image forKey:key]; + break; + case SDImageCacheTypeDisk: + [self.diskCache setData:imageData forKey:key]; + break; + case SDImageCacheTypeAll: + [self.memoryCache setObject:image forKey:key]; + [self.diskCache setData:imageData forKey:key]; + break; + default: + break; + } + if (completionBlock) { + completionBlock(); + } +} + ++ (nullable NSString *)userCacheDirectory { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + return paths.firstObject; +} + +@end diff --git a/Tests/Tests/SDWebImageTestLoader.h b/Tests/Tests/SDWebImageTestLoader.h index d6a3f5f9..bd343cd8 100644 --- a/Tests/Tests/SDWebImageTestLoader.h +++ b/Tests/Tests/SDWebImageTestLoader.h @@ -13,4 +13,6 @@ // A really naive implementation of custom image loader using `NSURLSession` @interface SDWebImageTestLoader : NSObject +@property (nonatomic, class, readonly, nonnull) SDWebImageTestLoader *sharedLoader; + @end diff --git a/Tests/Tests/SDWebImageTestLoader.m b/Tests/Tests/SDWebImageTestLoader.m index 22978edb..14f6f7e5 100644 --- a/Tests/Tests/SDWebImageTestLoader.m +++ b/Tests/Tests/SDWebImageTestLoader.m @@ -16,6 +16,15 @@ @implementation SDWebImageTestLoader ++ (SDWebImageTestLoader *)sharedLoader { + static dispatch_once_t onceToken; + static SDWebImageTestLoader *loader; + dispatch_once(&onceToken, ^{ + loader = [[SDWebImageTestLoader alloc] init]; + }); + return loader; +} + - (BOOL)canRequestImageForURL:(NSURL *)url { return YES; }