diff --git a/Cartfile b/Cartfile index 299439a..a93dfe6 100644 --- a/Cartfile +++ b/Cartfile @@ -1,3 +1,3 @@ -github "SDWebImage/SDWebImage" ~> 5.15 +github "SDWebImage/SDWebImage" ~> 5.18 github "ibireme/YYCache" ~> 1.0 github "ibireme/YYImage" ~> 1.0 diff --git a/SDWebImageYYPlugin.podspec b/SDWebImageYYPlugin.podspec index c6ff350..18bf3c5 100644 --- a/SDWebImageYYPlugin.podspec +++ b/SDWebImageYYPlugin.podspec @@ -31,7 +31,7 @@ TODO: Add long description of the pod here. s.source_files = 'SDWebImageYYPlugin/Module/SDWebImageYYPlugin.h' s.module_map = 'SDWebImageYYPlugin/Module/SDWebImageYYPlugin.modulemap' - s.dependency 'SDWebImage/Core', '~> 5.15' + s.dependency 'SDWebImage/Core', '~> 5.18' s.subspec 'YYCache' do |ss| ss.dependency 'YYCache' diff --git a/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYCache+SDAdditions.h b/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYCache+SDAdditions.h index f08232c..2c31637 100644 --- a/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYCache+SDAdditions.h +++ b/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYCache+SDAdditions.h @@ -12,4 +12,7 @@ /// YYCache category to support `SDImageCache` protocol. This allow user who prefer YYCache to be used as SDWebImage's custom image cache @interface YYCache (SDAdditions) +/// Cache Config object - storing all kind of settings. +@property (nonatomic, strong, readonly, nonnull) SDImageCacheConfig *config; + @end diff --git a/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYCache+SDAdditions.m b/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYCache+SDAdditions.m index 39bbe22..725ccfe 100644 --- a/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYCache+SDAdditions.m +++ b/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYCache+SDAdditions.m @@ -9,13 +9,76 @@ #import "YYCache+SDAdditions.h" #import "YYMemoryCache+SDAdditions.h" #import "YYDiskCache+SDAdditions.h" +#import "SDImageTransformer.h" // TODO, remove this +#import -static void SDYYPluginUnarchiveObject(NSData *data, UIImage *image) { - if (!data || !image) { +// TODO, remove this +static BOOL SDIsThumbnailKey(NSString *key) { + if ([key rangeOfString:@"-Thumbnail("].location != NSNotFound) { + return YES; + } + return NO; +} + +@implementation YYCache (SDAdditions) + +- (SDImageCacheConfig *)config { + // Provided the default one + SDImageCacheConfig *config = objc_getAssociatedObject(self, @selector(config)); + if (!config) { + config = SDImageCacheConfig.defaultCacheConfig; + config.shouldDisableiCloud + [self setConfig:config]; + } + return config; +} + +- (void)setConfig:(SDImageCacheConfig *)config { + objc_setAssociatedObject(self, @selector(config), config, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (void)_syncDiskToMemoryWithImage:(UIImage *)diskImage forKey:(NSString *)key { + // earily check + if (!self.config.shouldCacheImagesInMemory) { + return; + } + if (!diskImage) { + return; + } + // The disk -> memory sync logic, which should only store thumbnail image with thumbnail key + // However, caller (like SDWebImageManager) will query full key, with thumbnail size, and get thubmnail image + // We should add a check here, currently it's a hack + if (diskImage.sd_isThumbnail && !SDIsThumbnailKey(key)) { + SDImageCoderOptions *options = diskImage.sd_decodeOptions; + CGSize thumbnailSize = CGSizeZero; + NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize]; + if (thumbnailSizeValue != nil) { + #if SD_MAC + thumbnailSize = thumbnailSizeValue.sizeValue; + #else + thumbnailSize = thumbnailSizeValue.CGSizeValue; + #endif + } + BOOL preserveAspectRatio = YES; + NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio]; + if (preserveAspectRatioValue != nil) { + preserveAspectRatio = preserveAspectRatioValue.boolValue; + } + // Calculate the actual thumbnail key + NSString *thumbnailKey = SDThumbnailedKeyForKey(key, thumbnailSize, preserveAspectRatio); + // Override the sync key + key = thumbnailKey; + } + NSUInteger cost = diskImage.sd_memoryCost; + [self.memoryCache setObject:diskImage forKey:key cost:cost]; +} + +- (void)_unarchiveObjectWithImage:(UIImage *)image forKey:(NSString *)key { + if (!image || !key) { return; } // Check extended data - NSData *extendedData = [YYDiskCache getExtendedDataFromObject:data]; + NSData *extendedData = [self.diskCache extendedDataForKey:key]; if (!extendedData) { return; } @@ -41,8 +104,8 @@ static void SDYYPluginUnarchiveObject(NSData *data, UIImage *image) { image.sd_extendedObject = extendedObject; } -static void SDYYPluginArchiveObject(NSData *data, UIImage *image) { - if (!data || !image) { +- (void)_archivedDataWithImage:(UIImage *)image forKey:(NSString *)key { + if (!image || !key) { return; } // Check extended data @@ -68,12 +131,10 @@ static void SDYYPluginArchiveObject(NSData *data, UIImage *image) { } } if (extendedData) { - [YYDiskCache setExtendedData:extendedData toObject:data]; + [self.diskCache setExtendedData:extendedData forKey:key]; } } -@implementation YYCache (SDAdditions) - - (id)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context completion:(SDImageCacheQueryCompletionBlock)doneBlock { return [self queryImageForKey:key options:options context:context cacheType:SDImageCacheTypeAll completion:doneBlock]; } @@ -102,7 +163,7 @@ @implementation YYCache (SDAdditions) if (image) { if (options & SDWebImageDecodeFirstFrameOnly) { // Ensure static image - if (image.sd_isAnimated) { + if (image.sd_imageFrameCount > 1) { #if SD_MAC image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp]; #else @@ -156,30 +217,23 @@ @implementation YYCache (SDAdditions) // the image is from in-memory cache, but need image data diskImage = image; } else if (diskData) { - BOOL shouldCacheToMomery = YES; + BOOL shouldCacheToMemory = YES; if (context[SDWebImageContextStoreCacheType]) { SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue]; - shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory); + shouldCacheToMemory = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory); } - CGSize thumbnailSize = CGSizeZero; - NSValue *thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize]; - if (thumbnailSizeValue != nil) { - #if SD_MAC - thumbnailSize = thumbnailSizeValue.sizeValue; - #else - thumbnailSize = thumbnailSizeValue.CGSizeValue; - #endif - } - if (thumbnailSize.width > 0 && thumbnailSize.height > 0) { - // Query full size cache key which generate a thumbnail, should not write back to full size memory cache - shouldCacheToMomery = NO; + // Special case: If user query image in list for the same URL, to avoid decode and write **same** image object into disk cache multiple times, we query and check memory cache here again. + if (shouldCacheToMemory && self.config.shouldCacheImagesInMemory) { + diskImage = [self.memoryCache objectForKey:key]; } // decode image data only if in-memory cache missed - diskImage = SDImageCacheDecodeImageData(diskData, key, options, context); - SDYYPluginUnarchiveObject(diskData, diskImage); - if (shouldCacheToMomery && diskImage) { - NSUInteger cost = diskImage.sd_memoryCost; - [self.memoryCache setObject:diskImage forKey:key cost:cost]; + if (!diskImage) { + diskImage = SDImageCacheDecodeImageData(diskData, key, options, context); + [self _unarchiveObjectWithImage:diskImage forKey:key]; + // check if we need sync logic + if (shouldCacheToMemory) { + [self _syncDiskToMemoryWithImage:diskImage forKey:key]; + } } } return diskImage; @@ -264,8 +318,8 @@ - (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSStri } } NSData *data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:context[SDWebImageContextImageEncodeOptions]]; - SDYYPluginArchiveObject(data, image); [self.diskCache setObject:data forKey:key withBlock:^{ + [self _archivedDataWithImage:image forKey:key]; if (completionBlock) { [(queue ?: SDCallbackQueue.mainQueue) async:^{ completionBlock(); @@ -274,8 +328,8 @@ - (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSStri }]; }); } else { - SDYYPluginArchiveObject(data, image); [self.diskCache setObject:data forKey:key withBlock:^{ + [self _archivedDataWithImage:image forKey:key]; if (completionBlock) { [(queue ?: SDCallbackQueue.mainQueue) async:^{ completionBlock(); diff --git a/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYDiskCache+SDAdditions.h b/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYDiskCache+SDAdditions.h index b9b0df7..8417a79 100644 --- a/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYDiskCache+SDAdditions.h +++ b/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYDiskCache+SDAdditions.h @@ -11,4 +11,7 @@ /// YYDiskCache category to support `SDDiskCache` protocol. This allow user who prefer YYDiskCache to be used as SDWebImage's custom disk cache @interface YYDiskCache (SDAdditions) +/// Cache Config object - storing all kind of settings. +@property (nonatomic, strong, readonly, nullable) SDImageCacheConfig *config; + @end diff --git a/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYDiskCache+SDAdditions.m b/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYDiskCache+SDAdditions.m index dbe9c86..7887a17 100644 --- a/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYDiskCache+SDAdditions.m +++ b/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYDiskCache+SDAdditions.m @@ -10,8 +10,6 @@ @interface YYDiskCache () -@property (nonatomic, strong, nullable) SDImageCacheConfig *sd_config; - // Internal Headers - (NSString *)_filenameForKey:(NSString *)key; @@ -19,12 +17,12 @@ - (NSString *)_filenameForKey:(NSString *)key; @implementation YYDiskCache (SDAdditions) -- (SDImageCacheConfig *)sd_config { - return objc_getAssociatedObject(self, @selector(sd_config)); +- (SDImageCacheConfig *)config { + return objc_getAssociatedObject(self, @selector(config)); } -- (void)setSd_config:(SDImageCacheConfig *)sd_config { - objc_setAssociatedObject(self, @selector(sd_config), sd_config, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +- (void)setConfig:(SDImageCacheConfig *)config { + objc_setAssociatedObject(self, @selector(config), config, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - SDDiskCache @@ -32,7 +30,9 @@ - (void)setSd_config:(SDImageCacheConfig *)sd_config { - (instancetype)initWithCachePath:(NSString *)cachePath config:(SDImageCacheConfig *)config { self = [self initWithPath:cachePath inlineThreshold:0]; if (self) { - self.sd_config = config; + self.config = config; + self.ageLimit = config.maxDiskAge; + self.costLimit = config.maxDiskSize; } return self; } @@ -78,8 +78,8 @@ - (void)removeAllData { } - (void)removeExpiredData { - NSTimeInterval ageLimit = self.sd_config.maxDiskAge; - NSUInteger sizeLimit = self.sd_config.maxDiskSize; + NSTimeInterval ageLimit = self.config.maxDiskAge; + NSUInteger sizeLimit = self.config.maxDiskSize; [self trimToAge:ageLimit]; [self trimToCost:sizeLimit]; diff --git a/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYMemoryCache+SDAdditions.h b/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYMemoryCache+SDAdditions.h index d57c0b2..4ec6d23 100644 --- a/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYMemoryCache+SDAdditions.h +++ b/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYMemoryCache+SDAdditions.h @@ -11,4 +11,7 @@ /// YYMemoryCache category to support `SDMemoryCache` protocol. This allow user who prefer YYMemoryCache to be used as SDWebImage's custom memory cache @interface YYMemoryCache (SDAdditions) +/// Cache Config object - storing all kind of settings. +@property (nonatomic, strong, readonly, nullable) SDImageCacheConfig *config; + @end diff --git a/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYMemoryCache+SDAdditions.m b/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYMemoryCache+SDAdditions.m index fdf0907..4f72284 100644 --- a/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYMemoryCache+SDAdditions.m +++ b/SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYMemoryCache+SDAdditions.m @@ -8,20 +8,14 @@ #import "YYMemoryCache+SDAdditions.h" #import -@interface YYMemoryCache () - -@property (nonatomic, strong, nullable) SDImageCacheConfig *sd_config; - -@end - @implementation YYMemoryCache (SDAdditions) -- (SDImageCacheConfig *)sd_config { - return objc_getAssociatedObject(self, @selector(sd_config)); +- (SDImageCacheConfig *)config { + return objc_getAssociatedObject(self, @selector(config)); } -- (void)setSd_config:(SDImageCacheConfig *)sd_config { - objc_setAssociatedObject(self, @selector(sd_config), sd_config, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +- (void)setConfig:(SDImageCacheConfig *)config { + objc_setAssociatedObject(self, @selector(config), config, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - SDMemoryCache @@ -29,7 +23,7 @@ - (void)setSd_config:(SDImageCacheConfig *)sd_config { - (instancetype)initWithConfig:(SDImageCacheConfig *)config { self = [self init]; if (self) { - self.sd_config = config; + self.config = config; self.countLimit = config.maxMemoryCount; self.costLimit = config.maxMemoryCost; } diff --git a/SDWebImageYYPlugin/Classes/YYImage/YYImageBridge/YYImage+SDAdditions.m b/SDWebImageYYPlugin/Classes/YYImage/YYImageBridge/YYImage+SDAdditions.m index 8c803ed..0616c47 100644 --- a/SDWebImageYYPlugin/Classes/YYImage/YYImageBridge/YYImage+SDAdditions.m +++ b/SDWebImageYYPlugin/Classes/YYImage/YYImageBridge/YYImage+SDAdditions.m @@ -19,6 +19,8 @@ static inline SDImageFormat SDImageFormatFromYYImageType(YYImageType type) { return SDImageFormatGIF; case YYImageTypeTIFF: return SDImageFormatTIFF; + case YYImageTypeBMP: + return SDImageFormatBMP; case YYImageTypeWebP: return SDImageFormatWebP; default: @@ -57,6 +59,10 @@ - (BOOL)isAllFramesLoaded { return self.preloadAllAnimatedImageFrames; } +- (SDImageFormat)animatedImageFormat { + return SDImageFormatFromYYImageType(self.animatedImageType); +} + @end @implementation YYImage (MemoryCacheCost) @@ -86,7 +92,7 @@ - (NSUInteger)sd_memoryCost { @implementation YYImage (Metadata) - (BOOL)sd_isAnimated { - return YES; + return self.animatedImageFrameCount > 1; } - (NSUInteger)sd_imageLoopCount { @@ -97,8 +103,22 @@ - (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount { return; } +- (NSUInteger)sd_imageFrameCount { + NSUInteger frameCount = self.animatedImageFrameCount; + if (frameCount > 1) { + return frameCount; + } else { + return 1; + } +} + - (SDImageFormat)sd_imageFormat { - return SDImageFormatFromYYImageType(self.animatedImageType); + NSData *animatedImageData = self.animatedImageData; + if (animatedImageData) { + return self.animatedImageFormat; + } else { + return [super sd_imageFormat]; + } } - (void)setSd_imageFormat:(SDImageFormat)sd_imageFormat { @@ -110,3 +130,69 @@ - (BOOL)sd_isVector { } @end + +@implementation YYImage (MultiFormat) + ++ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data { + return [self sd_imageWithData:data scale:1]; +} + ++ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data scale:(CGFloat)scale { + return [self sd_imageWithData:data scale:scale firstFrameOnly:NO]; +} + ++ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data scale:(CGFloat)scale firstFrameOnly:(BOOL)firstFrameOnly { + if (!data) { + return nil; + } + return [[self alloc] initWithData:data scale:scale options:@{SDImageCoderDecodeFirstFrameOnly : @(firstFrameOnly)}]; +} + +- (nullable NSData *)sd_imageData { + NSData *imageData = self.animatedImageData; + if (imageData) { + return imageData; + } else { + return [self sd_imageDataAsFormat:self.animatedImageFormat]; + } +} + +- (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat { + return [self sd_imageDataAsFormat:imageFormat compressionQuality:1]; +} + +- (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat compressionQuality:(double)compressionQuality { + return [self sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:NO]; +} + +- (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat compressionQuality:(double)compressionQuality firstFrameOnly:(BOOL)firstFrameOnly { + // Protect when user input the imageFormat == self.animatedImageFormat && compressionQuality == 1 + // This should be treated as grabbing `self.animatedImageData` as well :) + NSData *imageData; + if (imageFormat == self.animatedImageFormat && compressionQuality == 1) { + imageData = self.animatedImageData; + } + if (imageData) return imageData; + + SDImageCoderOptions *options = @{SDImageCoderEncodeCompressionQuality : @(compressionQuality), SDImageCoderEncodeFirstFrameOnly : @(firstFrameOnly)}; + NSUInteger frameCount = self.animatedImageFrameCount; + if (frameCount <= 1) { + // Static image + imageData = [SDImageCodersManager.sharedManager encodedDataWithImage:self format:imageFormat options:options]; + } + if (imageData) return imageData; + + NSUInteger loopCount = self.animatedImageLoopCount; + // Keep animated image encoding, loop each frame. + NSMutableArray *frames = [NSMutableArray arrayWithCapacity:frameCount]; + for (size_t i = 0; i < frameCount; i++) { + UIImage *image = [self animatedImageFrameAtIndex:i]; + NSTimeInterval duration = [self animatedImageDurationAtIndex:i]; + SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration]; + [frames addObject:frame]; + } + imageData = [SDImageCodersManager.sharedManager encodedDataWithFrames:frames loopCount:loopCount format:imageFormat options:options]; + return imageData; +} + +@end