// AFAutoPurgingImageCache.m // Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #if TARGET_OS_IOS || TARGET_OS_TV #import "AFAutoPurgingImageCache.h" @interface AFCachedImage : NSObject @property (nonatomic, strong) UIImage *image; @property (nonatomic, strong) NSString *identifier; @property (nonatomic, assign) UInt64 totalBytes; @property (nonatomic, strong) NSDate *lastAccessDate; @property (nonatomic, assign) UInt64 currentMemoryUsage; @end @implementation AFCachedImage -(instancetype)initWithImage:(UIImage *)image identifier:(NSString *)identifier { if (self = [self init]) { self.image = image; self.identifier = identifier; CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale); CGFloat bytesPerPixel = 4.0; CGFloat bytesPerSize = imageSize.width * imageSize.height; self.totalBytes = (UInt64)bytesPerPixel * (UInt64)bytesPerSize; self.lastAccessDate = [NSDate date]; } return self; } - (UIImage*)accessImage { self.lastAccessDate = [NSDate date]; return self.image; } - (NSString *)description { NSString *descriptionString = [NSString stringWithFormat:@"Idenfitier: %@ lastAccessDate: %@ ", self.identifier, self.lastAccessDate]; return descriptionString; } @end @interface AFAutoPurgingImageCache () @property (nonatomic, strong) NSMutableDictionary *cachedImages; @property (nonatomic, assign) UInt64 currentMemoryUsage; @property (nonatomic, strong) dispatch_queue_t synchronizationQueue; @end @implementation AFAutoPurgingImageCache - (instancetype)init { return [self initWithMemoryCapacity:100 * 1024 * 1024 preferredMemoryCapacity:60 * 1024 * 1024]; } - (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity { if (self = [super init]) { self.memoryCapacity = memoryCapacity; self.preferredMemoryUsageAfterPurge = preferredMemoryCapacity; self.cachedImages = [[NSMutableDictionary alloc] init]; NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]]; self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllImages) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (UInt64)memoryUsage { __block UInt64 result = 0; dispatch_sync(self.synchronizationQueue, ^{ result = self.currentMemoryUsage; }); return result; } - (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier { dispatch_barrier_async(self.synchronizationQueue, ^{ AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier]; AFCachedImage *previousCachedImage = self.cachedImages[identifier]; if (previousCachedImage != nil) { self.currentMemoryUsage -= previousCachedImage.totalBytes; } self.cachedImages[identifier] = cacheImage; self.currentMemoryUsage += cacheImage.totalBytes; }); dispatch_barrier_async(self.synchronizationQueue, ^{ if (self.currentMemoryUsage > self.memoryCapacity) { UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge; NSMutableArray *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues]; NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate" ascending:YES]; [sortedImages sortUsingDescriptors:@[sortDescriptor]]; UInt64 bytesPurged = 0; for (AFCachedImage *cachedImage in sortedImages) { [self.cachedImages removeObjectForKey:cachedImage.identifier]; bytesPurged += cachedImage.totalBytes; if (bytesPurged >= bytesToPurge) { break ; } } self.currentMemoryUsage -= bytesPurged; } }); } - (BOOL)removeImageWithIdentifier:(NSString *)identifier { __block BOOL removed = NO; dispatch_barrier_sync(self.synchronizationQueue, ^{ AFCachedImage *cachedImage = self.cachedImages[identifier]; if (cachedImage != nil) { [self.cachedImages removeObjectForKey:identifier]; self.currentMemoryUsage -= cachedImage.totalBytes; removed = YES; } }); return removed; } - (BOOL)removeAllImages { __block BOOL removed = NO; dispatch_barrier_sync(self.synchronizationQueue, ^{ if (self.cachedImages.count > 0) { [self.cachedImages removeAllObjects]; self.currentMemoryUsage = 0; removed = YES; } }); return removed; } - (nullable UIImage *)imageWithIdentifier:(NSString *)identifier { __block UIImage *image = nil; dispatch_sync(self.synchronizationQueue, ^{ AFCachedImage *cachedImage = self.cachedImages[identifier]; image = [cachedImage accessImage]; }); return image; } - (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier { [self addImage:image withIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]]; } - (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier { return [self removeImageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]]; } - (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier { return [self imageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]]; } - (NSString *)imageCacheKeyFromURLRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)additionalIdentifier { NSString *key = request.URL.absoluteString; if (additionalIdentifier != nil) { key = [key stringByAppendingString:additionalIdentifier]; } return key; } - (BOOL)shouldCacheImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier { return YES; } @end #endif