From 6dd92d11ff99138297614d1b0204b21b9a231096 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 3 Jan 2019 15:11:24 +0800 Subject: [PATCH] Use the memory bytes size, instead of pixel size to calculate the memory cost function --- SDWebImage/SDAnimatedImage.m | 30 ++++++++++++++++++++++++++++ SDWebImage/SDImageCacheConfig.h | 5 +++-- SDWebImage/UIImage+MemoryCacheCost.h | 7 ++++--- SDWebImage/UIImage+MemoryCacheCost.m | 14 ++++++++++--- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/SDWebImage/SDAnimatedImage.m b/SDWebImage/SDAnimatedImage.m index d2df36d0..81148d08 100644 --- a/SDWebImage/SDAnimatedImage.m +++ b/SDWebImage/SDAnimatedImage.m @@ -11,6 +11,8 @@ #import "SDImageCoder.h" #import "SDImageCodersManager.h" #import "SDImageFrame.h" +#import "UIImage+MemoryCacheCost.h" +#import "objc/runtime.h" static CGFloat SDImageScaleFromPath(NSString *string) { if (string.length == 0 || [string hasSuffix:@"/"]) return 1; @@ -426,3 +428,31 @@ static NSArray *SDBundlePreferredScales() { } @end + +@interface SDAnimatedImage (MemoryCacheCost) + +@end + +@implementation SDAnimatedImage (MemoryCacheCost) + +- (NSUInteger)sd_imageMemoryCost { + NSNumber *value = objc_getAssociatedObject(self, @selector(sd_memoryCost)); + if (value != nil) { + return value.unsignedIntegerValue; + } + + CGImageRef imageRef = self.CGImage; + if (!imageRef) { + return 0; + } + NSUInteger bytesPerFrame = CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef); + NSUInteger frameCount = 1; + if (self.isAllFramesLoaded) { + frameCount = self.animatedImageFrameCount; + } + frameCount = frameCount > 0 ? frameCount : 1; + NSUInteger cost = bytesPerFrame * frameCount; + return cost; +} + +@end diff --git a/SDWebImage/SDImageCacheConfig.h b/SDWebImage/SDImageCacheConfig.h index 24f47c45..6d24ad93 100644 --- a/SDWebImage/SDImageCacheConfig.h +++ b/SDWebImage/SDImageCacheConfig.h @@ -82,13 +82,14 @@ typedef NS_ENUM(NSUInteger, SDImageCacheConfigExpireType) { @property (assign, nonatomic) NSUInteger maxCacheSize; /** - * The maximum "total cost" of the in-memory image cache. The cost function is the number of pixels held in memory. + * The maximum "total cost" of the in-memory image cache. The cost function is the bytes size held in memory. + * @note The memory cost is bytes size in memory, but not simple pixels count. For common ARGB8888 image, one pixel is 4 bytes (32 bits). * Defaults to 0. Which means there is no memory cost limit. */ @property (assign, nonatomic) NSUInteger maxMemoryCost; /** - * The maximum number of objects the cache should hold. + * The maximum number of objects in-memory image cache should hold. * Defaults to 0. Which means there is no memory count limit. */ @property (assign, nonatomic) NSUInteger maxMemoryCount; diff --git a/SDWebImage/UIImage+MemoryCacheCost.h b/SDWebImage/UIImage+MemoryCacheCost.h index 9ecf5b94..ba63928e 100644 --- a/SDWebImage/UIImage+MemoryCacheCost.h +++ b/SDWebImage/UIImage+MemoryCacheCost.h @@ -11,12 +11,13 @@ @interface UIImage (MemoryCacheCost) /** - The memory cache cost for specify image used by image cache. The cost function is the pixles count held in memory. + The memory cache cost for specify image used by image cache. The cost function is the bytes size held in memory. If you set some associated object to `UIImage`, you can set the custom value to indicate the memory cost. - For `UIImage`, this method return the single frame pixles count when `image.images` is nil for static image. Retuen full frame pixels count when `image.images` is not nil for animated image. - For `NSImage`, this method return the single frame pixels count because `NSImage` does not store all frames in memory. + For `UIImage`, this method return the single frame bytes size when `image.images` is nil for static image. Retuen full frame bytes size when `image.images` is not nil for animated image. + For `NSImage`, this method return the single frame bytes size because `NSImage` does not store all frames in memory. @note Note that because of the limitations of categories this property can get out of sync if you create another instance with CGImage or other methods. + @note For custom animated class conforms to `SDAnimatedImage`, you can override this getter method in your subclass to return a more proper value instead, which representing the current frames' total bytes. */ @property (assign, nonatomic) NSUInteger sd_memoryCost; diff --git a/SDWebImage/UIImage+MemoryCacheCost.m b/SDWebImage/UIImage+MemoryCacheCost.m index 6f1375d7..b0883b1f 100644 --- a/SDWebImage/UIImage+MemoryCacheCost.m +++ b/SDWebImage/UIImage+MemoryCacheCost.m @@ -8,14 +8,22 @@ #import "UIImage+MemoryCacheCost.h" #import "objc/runtime.h" +#import "NSImage+Compatibility.h" FOUNDATION_STATIC_INLINE NSUInteger SDMemoryCacheCostForImage(UIImage *image) { + CGImageRef imageRef = image.CGImage; + if (!imageRef) { + return 0; + } + NSUInteger bytesPerFrame = CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef); + NSUInteger frameCount; #if SD_MAC - return image.size.height * image.size.width; + frameCount = 1; #elif SD_UIKIT || SD_WATCH - NSUInteger imageSize = image.size.height * image.size.width * image.scale * image.scale; - return image.images ? (imageSize * image.images.count) : imageSize; + frameCount = image.images.count > 0 ? image.images.count : 1; #endif + NSUInteger cost = bytesPerFrame * frameCount; + return cost; } @implementation UIImage (MemoryCacheCost)