YYImageCache.m 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. //
  2. // YYImageCache.m
  3. // YYKit <https://github.com/ibireme/YYKit>
  4. //
  5. // Created by ibireme on 15/2/15.
  6. // Copyright (c) 2015 ibireme.
  7. //
  8. // This source code is licensed under the MIT-style license found in the
  9. // LICENSE file in the root directory of this source tree.
  10. //
  11. #import "YYImageCache.h"
  12. #import "YYMemoryCache.h"
  13. #import "YYDiskCache.h"
  14. #import "UIImage+YYAdd.h"
  15. #import "NSObject+YYAdd.h"
  16. #import "YYImage.h"
  17. #if __has_include("YYDispatchQueuePool.h")
  18. #import "YYDispatchQueuePool.h"
  19. #endif
  20. static inline dispatch_queue_t YYImageCacheIOQueue() {
  21. #ifdef YYDispatchQueuePool_h
  22. return YYDispatchQueueGetForQOS(NSQualityOfServiceDefault);
  23. #else
  24. return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  25. #endif
  26. }
  27. static inline dispatch_queue_t YYImageCacheDecodeQueue() {
  28. #ifdef YYDispatchQueuePool_h
  29. return YYDispatchQueueGetForQOS(NSQualityOfServiceUtility);
  30. #else
  31. return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
  32. #endif
  33. }
  34. @interface YYImageCache ()
  35. - (NSUInteger)imageCost:(UIImage *)image;
  36. - (UIImage *)imageFromData:(NSData *)data;
  37. @end
  38. @implementation YYImageCache
  39. - (NSUInteger)imageCost:(UIImage *)image {
  40. CGImageRef cgImage = image.CGImage;
  41. if (!cgImage) return 1;
  42. CGFloat height = CGImageGetHeight(cgImage);
  43. size_t bytesPerRow = CGImageGetBytesPerRow(cgImage);
  44. NSUInteger cost = bytesPerRow * height;
  45. if (cost == 0) cost = 1;
  46. return cost;
  47. }
  48. - (UIImage *)imageFromData:(NSData *)data {
  49. NSData *scaleData = [YYDiskCache getExtendedDataFromObject:data];
  50. CGFloat scale = 0;
  51. if (scaleData) {
  52. scale = ((NSNumber *)[NSKeyedUnarchiver unarchiveObjectWithData:scaleData]).doubleValue;
  53. }
  54. if (scale <= 0) scale = [UIScreen mainScreen].scale;
  55. UIImage *image;
  56. if (_allowAnimatedImage) {
  57. image = [[YYImage alloc] initWithData:data scale:scale];
  58. if (_decodeForDisplay) image = [image imageByDecoded];
  59. } else {
  60. YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];
  61. image = [decoder frameAtIndex:0 decodeForDisplay:_decodeForDisplay].image;
  62. }
  63. return image;
  64. }
  65. #pragma mark Public
  66. + (instancetype)sharedCache {
  67. static YYImageCache *cache = nil;
  68. static dispatch_once_t onceToken;
  69. dispatch_once(&onceToken, ^{
  70. NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
  71. NSUserDomainMask, YES) firstObject];
  72. cachePath = [cachePath stringByAppendingPathComponent:@"com.ibireme.yykit"];
  73. cachePath = [cachePath stringByAppendingPathComponent:@"images"];
  74. cache = [[self alloc] initWithPath:cachePath];
  75. });
  76. return cache;
  77. }
  78. - (instancetype)init {
  79. @throw [NSException exceptionWithName:@"YYImageCache init error" reason:@"YYImageCache must be initialized with a path. Use 'initWithPath:' instead." userInfo:nil];
  80. return [self initWithPath:@""];
  81. }
  82. - (instancetype)initWithPath:(NSString *)path {
  83. YYMemoryCache *memoryCache = [YYMemoryCache new];
  84. memoryCache.shouldRemoveAllObjectsOnMemoryWarning = YES;
  85. memoryCache.shouldRemoveAllObjectsWhenEnteringBackground = YES;
  86. memoryCache.countLimit = NSUIntegerMax;
  87. memoryCache.costLimit = NSUIntegerMax;
  88. memoryCache.ageLimit = 12 * 60 * 60;
  89. YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path];
  90. diskCache.customArchiveBlock = ^(id object) { return (NSData *)object; };
  91. diskCache.customUnarchiveBlock = ^(NSData *data) { return (id)data; };
  92. if (!memoryCache || !diskCache) return nil;
  93. self = [super init];
  94. _memoryCache = memoryCache;
  95. _diskCache = diskCache;
  96. _allowAnimatedImage = YES;
  97. _decodeForDisplay = YES;
  98. return self;
  99. }
  100. - (void)setImage:(UIImage *)image forKey:(NSString *)key {
  101. [self setImage:image imageData:nil forKey:key withType:YYImageCacheTypeAll];
  102. }
  103. - (void)setImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key withType:(YYImageCacheType)type {
  104. if (!key || (image == nil && imageData.length == 0)) return;
  105. __weak typeof(self) _self = self;
  106. if (type & YYImageCacheTypeMemory) { // add to memory cache
  107. if (image) {
  108. if (image.isDecodedForDisplay) {
  109. [_memoryCache setObject:image forKey:key withCost:[_self imageCost:image]];
  110. } else {
  111. dispatch_async(YYImageCacheDecodeQueue(), ^{
  112. __strong typeof(_self) self = _self;
  113. if (!self) return;
  114. [self.memoryCache setObject:[image imageByDecoded] forKey:key withCost:[self imageCost:image]];
  115. });
  116. }
  117. } else if (imageData) {
  118. dispatch_async(YYImageCacheDecodeQueue(), ^{
  119. __strong typeof(_self) self = _self;
  120. if (!self) return;
  121. UIImage *newImage = [self imageFromData:imageData];
  122. [self.memoryCache setObject:newImage forKey:key withCost:[self imageCost:newImage]];
  123. });
  124. }
  125. }
  126. if (type & YYImageCacheTypeDisk) { // add to disk cache
  127. if (imageData) {
  128. if (image) {
  129. [YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:imageData];
  130. }
  131. [_diskCache setObject:imageData forKey:key];
  132. } else if (image) {
  133. dispatch_async(YYImageCacheIOQueue(), ^{
  134. __strong typeof(_self) self = _self;
  135. if (!self) return;
  136. NSData *data = [image imageDataRepresentation];
  137. [YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:data];
  138. [self.diskCache setObject:data forKey:key];
  139. });
  140. }
  141. }
  142. }
  143. - (void)removeImageForKey:(NSString *)key {
  144. [self removeImageForKey:key withType:YYImageCacheTypeAll];
  145. }
  146. - (void)removeImageForKey:(NSString *)key withType:(YYImageCacheType)type {
  147. if (type & YYImageCacheTypeMemory) [_memoryCache removeObjectForKey:key];
  148. if (type & YYImageCacheTypeDisk) [_diskCache removeObjectForKey:key];
  149. }
  150. - (BOOL)containsImageForKey:(NSString *)key {
  151. return [self containsImageForKey:key withType:YYImageCacheTypeAll];
  152. }
  153. - (BOOL)containsImageForKey:(NSString *)key withType:(YYImageCacheType)type {
  154. if (type & YYImageCacheTypeMemory) {
  155. if ([_memoryCache containsObjectForKey:key]) return YES;
  156. }
  157. if (type & YYImageCacheTypeDisk) {
  158. if ([_diskCache containsObjectForKey:key]) return YES;
  159. }
  160. return NO;
  161. }
  162. - (UIImage *)getImageForKey:(NSString *)key {
  163. return [self getImageForKey:key withType:YYImageCacheTypeAll];
  164. }
  165. - (UIImage *)getImageForKey:(NSString *)key withType:(YYImageCacheType)type {
  166. if (!key) return nil;
  167. if (type & YYImageCacheTypeMemory) {
  168. UIImage *image = [_memoryCache objectForKey:key];
  169. if (image) return image;
  170. }
  171. if (type & YYImageCacheTypeDisk) {
  172. NSData *data = (id)[_diskCache objectForKey:key];
  173. UIImage *image = [self imageFromData:data];
  174. if (image && (type & YYImageCacheTypeMemory)) {
  175. [_memoryCache setObject:image forKey:key withCost:[self imageCost:image]];
  176. }
  177. return image;
  178. }
  179. return nil;
  180. }
  181. - (void)getImageForKey:(NSString *)key withType:(YYImageCacheType)type withBlock:(void (^)(UIImage *image, YYImageCacheType type))block {
  182. if (!block) return;
  183. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  184. UIImage *image = nil;
  185. if (type & YYImageCacheTypeMemory) {
  186. image = [_memoryCache objectForKey:key];
  187. if (image) {
  188. dispatch_async(dispatch_get_main_queue(), ^{
  189. block(image, YYImageCacheTypeMemory);
  190. });
  191. return;
  192. }
  193. }
  194. if (type & YYImageCacheTypeDisk) {
  195. NSData *data = (id)[_diskCache objectForKey:key];
  196. image = [self imageFromData:data];
  197. if (image) {
  198. [_memoryCache setObject:image forKey:key];
  199. dispatch_async(dispatch_get_main_queue(), ^{
  200. block(image, YYImageCacheTypeDisk);
  201. });
  202. return;
  203. }
  204. }
  205. dispatch_async(dispatch_get_main_queue(), ^{
  206. block(nil, YYImageCacheTypeNone);
  207. });
  208. });
  209. }
  210. - (NSData *)getImageDataForKey:(NSString *)key {
  211. return (id)[_diskCache objectForKey:key];
  212. }
  213. - (void)getImageDataForKey:(NSString *)key withBlock:(void (^)(NSData *imageData))block {
  214. if (!block) return;
  215. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  216. NSData *data = (id)[_diskCache objectForKey:key];
  217. dispatch_async(dispatch_get_main_queue(), ^{
  218. block(data);
  219. });
  220. });
  221. }
  222. @end