AFAutoPurgingImageCache.m 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // AFAutoPurgingImageCache.m
  2. // Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. #import <TargetConditionals.h>
  22. #if TARGET_OS_IOS || TARGET_OS_TV
  23. #import "AFAutoPurgingImageCache.h"
  24. @interface AFCachedImage : NSObject
  25. @property (nonatomic, strong) UIImage *image;
  26. @property (nonatomic, copy) NSString *identifier;
  27. @property (nonatomic, assign) UInt64 totalBytes;
  28. @property (nonatomic, strong) NSDate *lastAccessDate;
  29. @property (nonatomic, assign) UInt64 currentMemoryUsage;
  30. @end
  31. @implementation AFCachedImage
  32. - (instancetype)initWithImage:(UIImage *)image identifier:(NSString *)identifier {
  33. if (self = [self init]) {
  34. self.image = image;
  35. self.identifier = identifier;
  36. CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
  37. CGFloat bytesPerPixel = 4.0;
  38. CGFloat bytesPerSize = imageSize.width * imageSize.height;
  39. self.totalBytes = (UInt64)bytesPerPixel * (UInt64)bytesPerSize;
  40. self.lastAccessDate = [NSDate date];
  41. }
  42. return self;
  43. }
  44. - (UIImage *)accessImage {
  45. self.lastAccessDate = [NSDate date];
  46. return self.image;
  47. }
  48. - (NSString *)description {
  49. NSString *descriptionString = [NSString stringWithFormat:@"Idenfitier: %@ lastAccessDate: %@ ", self.identifier, self.lastAccessDate];
  50. return descriptionString;
  51. }
  52. @end
  53. @interface AFAutoPurgingImageCache ()
  54. @property (nonatomic, strong) NSMutableDictionary <NSString* , AFCachedImage*> *cachedImages;
  55. @property (nonatomic, assign) UInt64 currentMemoryUsage;
  56. @property (nonatomic, strong) dispatch_queue_t synchronizationQueue;
  57. @end
  58. @implementation AFAutoPurgingImageCache
  59. - (instancetype)init {
  60. return [self initWithMemoryCapacity:100 * 1024 * 1024 preferredMemoryCapacity:60 * 1024 * 1024];
  61. }
  62. - (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity {
  63. if (self = [super init]) {
  64. self.memoryCapacity = memoryCapacity;
  65. self.preferredMemoryUsageAfterPurge = preferredMemoryCapacity;
  66. self.cachedImages = [[NSMutableDictionary alloc] init];
  67. NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]];
  68. self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
  69. [[NSNotificationCenter defaultCenter]
  70. addObserver:self
  71. selector:@selector(removeAllImages)
  72. name:UIApplicationDidReceiveMemoryWarningNotification
  73. object:nil];
  74. }
  75. return self;
  76. }
  77. - (void)dealloc {
  78. [[NSNotificationCenter defaultCenter] removeObserver:self];
  79. }
  80. - (UInt64)memoryUsage {
  81. __block UInt64 result = 0;
  82. dispatch_sync(self.synchronizationQueue, ^{
  83. result = self.currentMemoryUsage;
  84. });
  85. return result;
  86. }
  87. - (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
  88. dispatch_barrier_async(self.synchronizationQueue, ^{
  89. AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
  90. AFCachedImage *previousCachedImage = self.cachedImages[identifier];
  91. if (previousCachedImage != nil) {
  92. self.currentMemoryUsage -= previousCachedImage.totalBytes;
  93. }
  94. self.cachedImages[identifier] = cacheImage;
  95. self.currentMemoryUsage += cacheImage.totalBytes;
  96. });
  97. dispatch_barrier_async(self.synchronizationQueue, ^{
  98. if (self.currentMemoryUsage > self.memoryCapacity) {
  99. UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge;
  100. NSMutableArray <AFCachedImage*> *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues];
  101. NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate"
  102. ascending:YES];
  103. [sortedImages sortUsingDescriptors:@[sortDescriptor]];
  104. UInt64 bytesPurged = 0;
  105. for (AFCachedImage *cachedImage in sortedImages) {
  106. [self.cachedImages removeObjectForKey:cachedImage.identifier];
  107. bytesPurged += cachedImage.totalBytes;
  108. if (bytesPurged >= bytesToPurge) {
  109. break;
  110. }
  111. }
  112. self.currentMemoryUsage -= bytesPurged;
  113. }
  114. });
  115. }
  116. - (BOOL)removeImageWithIdentifier:(NSString *)identifier {
  117. __block BOOL removed = NO;
  118. dispatch_barrier_sync(self.synchronizationQueue, ^{
  119. AFCachedImage *cachedImage = self.cachedImages[identifier];
  120. if (cachedImage != nil) {
  121. [self.cachedImages removeObjectForKey:identifier];
  122. self.currentMemoryUsage -= cachedImage.totalBytes;
  123. removed = YES;
  124. }
  125. });
  126. return removed;
  127. }
  128. - (BOOL)removeAllImages {
  129. __block BOOL removed = NO;
  130. dispatch_barrier_sync(self.synchronizationQueue, ^{
  131. if (self.cachedImages.count > 0) {
  132. [self.cachedImages removeAllObjects];
  133. self.currentMemoryUsage = 0;
  134. removed = YES;
  135. }
  136. });
  137. return removed;
  138. }
  139. - (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
  140. __block UIImage *image = nil;
  141. dispatch_sync(self.synchronizationQueue, ^{
  142. AFCachedImage *cachedImage = self.cachedImages[identifier];
  143. image = [cachedImage accessImage];
  144. });
  145. return image;
  146. }
  147. - (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
  148. [self addImage:image withIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
  149. }
  150. - (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
  151. return [self removeImageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
  152. }
  153. - (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
  154. return [self imageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
  155. }
  156. - (NSString *)imageCacheKeyFromURLRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)additionalIdentifier {
  157. NSString *key = request.URL.absoluteString;
  158. if (additionalIdentifier != nil) {
  159. key = [key stringByAppendingString:additionalIdentifier];
  160. }
  161. return key;
  162. }
  163. - (BOOL)shouldCacheImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier {
  164. return YES;
  165. }
  166. @end
  167. #endif