17 #import "NIInMemoryCache.h"
19 #import "NIDebuggingTools.h"
20 #import "NIPreprocessorMacros.h"
22 #import <UIKit/UIKit.h>
24 #if !defined(__has_feature) || !__has_feature(objc_arc)
25 #error "Nimbus requires ARC support."
30 @property (nonatomic, strong) NSMutableDictionary* cacheMap;
32 @property (nonatomic, strong) NSMutableOrderedSet* lruCacheObjects;
40 @interface NIMemoryCacheInfo : NSObject
45 @property (nonatomic, copy) NSString* name;
50 @property (nonatomic, strong)
id object;
55 @property (nonatomic, strong) NSDate* expirationDate;
65 @property (nonatomic, strong) NSDate* lastAccessTime;
81 [[NSNotificationCenter defaultCenter] removeObserver:self];
85 return [
self initWithCapacity:0];
88 - (id)initWithCapacity:(NSUInteger)capacity {
89 if ((
self = [super init])) {
90 _cacheMap = [[NSMutableDictionary alloc] initWithCapacity:capacity];
91 _lruCacheObjects = [NSMutableOrderedSet orderedSet];
94 [[NSNotificationCenter defaultCenter] addObserver:self
95 selector:@selector(reduceMemoryUsage)
96 name:UIApplicationDidReceiveMemoryWarningNotification
102 - (NSString *)description {
103 return [NSString stringWithFormat:
109 self.lruCacheObjects,
113 #pragma mark - Internal
115 - (void)updateAccessTimeForInfo:(NIMemoryCacheInfo *)info {
116 @
synchronized(
self) {
117 NIDASSERT(nil != info);
121 info.lastAccessTime = [NSDate date];
123 [
self.lruCacheObjects removeObject:info];
124 [
self.lruCacheObjects addObject:info];
128 - (NIMemoryCacheInfo *)cacheInfoForName:(NSString *)name {
129 NIMemoryCacheInfo* info;
130 @
synchronized(
self) {
131 info =
self.cacheMap[name];
136 - (void)setCacheInfo:(NIMemoryCacheInfo *)info forName:(NSString *)name {
137 @
synchronized(
self) {
138 NIDASSERT(nil != name);
144 [
self updateAccessTimeForInfo:info];
146 id previousObject = [
self cacheInfoForName:name].object;
147 if ([
self shouldSetObject:info.object withName:name previousObject:previousObject]) {
148 self.cacheMap[name] = info;
149 [
self didSetObject:info.object withName:name];
154 - (void)removeCacheInfoForName:(NSString *)name {
155 @
synchronized(
self) {
156 NIDASSERT(nil != name);
161 NIMemoryCacheInfo* cacheInfo = [
self cacheInfoForName:name];
162 [
self willRemoveObject:cacheInfo.object withName:name];
164 [
self.lruCacheObjects removeObject:cacheInfo];
165 [
self.cacheMap removeObjectForKey:name];
169 #pragma mark - Subclassing
172 - (BOOL)willSetObject:(
id)object withName:(NSString *)name previousObject:(
id)previousObject {
176 - (BOOL)shouldSetObject:(
id)object withName:(NSString *)name previousObject:(
id)previousObject {
181 - (void)didSetObject:(
id)object withName:(NSString *)name {
185 - (void)willRemoveObject:(
id)object withName:(NSString *)name {
189 #pragma mark - Public
191 - (void)storeObject:(
id)object withName:(NSString *)name {
192 @
synchronized(
self) {
197 - (void)storeObject:(
id)object withName:(NSString *)name expiresAfter:(NSDate *)expirationDate {
198 @
synchronized(
self) {
204 if (nil != expirationDate && [[NSDate date] timeIntervalSinceDate:expirationDate] >= 0) {
212 NIMemoryCacheInfo* info = [
self cacheInfoForName:name];
216 info = [[NIMemoryCacheInfo alloc] init];
221 info.object = object;
224 info.expirationDate = expirationDate;
227 [
self setCacheInfo:info forName:name];
231 - (id)objectWithName:(NSString *)name {
232 @
synchronized(
self) {
233 NIMemoryCacheInfo* info = [
self cacheInfoForName:name];
238 if ([info hasExpired]) {
243 [
self updateAccessTimeForInfo:info];
245 object = info.object;
253 - (BOOL)containsObjectWithName:(NSString *)name {
254 @
synchronized(
self) {
255 NIMemoryCacheInfo* info = [
self cacheInfoForName:name];
257 if ([info hasExpired]) {
262 return (nil != info);
266 - (NSDate *)dateOfLastAccessWithName:(NSString *)name {
267 @
synchronized(
self) {
268 NIMemoryCacheInfo* info = [
self cacheInfoForName:name];
270 if ([info hasExpired]) {
275 return [info lastAccessTime];
280 @
synchronized(
self) {
281 NIMemoryCacheInfo* info = [
self.lruCacheObjects firstObject];
283 if ([info hasExpired]) {
293 @
synchronized(
self) {
294 NIMemoryCacheInfo* info = [
self.lruCacheObjects lastObject];
296 if ([info hasExpired]) {
305 - (void)removeObjectWithName:(NSString *)name {
306 @
synchronized(
self) {
307 [
self removeCacheInfoForName:name];
311 - (void)removeAllObjectsWithPrefix:(NSString *)prefix {
312 @
synchronized(
self) {
314 for (NSString* name in [
self.cacheMap copy]) {
315 if ([name hasPrefix:prefix]) {
323 @
synchronized(
self) {
330 @
synchronized(
self) {
332 for (
id name in [
self.cacheMap copy]) {
333 NIMemoryCacheInfo* info = [
self cacheInfoForName:name];
335 if ([info hasExpired]) {
336 [
self removeCacheInfoForName:name];
343 @
synchronized(
self) {
344 return self.cacheMap.count;
350 @implementation NIMemoryCacheInfo
353 return (nil != _expirationDate
354 && [[NSDate date] timeIntervalSinceDate:_expirationDate] >= 0);
357 - (NSString *)description {
358 return [NSString stringWithFormat:
362 @" expiration date: %@"
363 @" last access time: %@"
369 self.lastAccessTime];
380 - (
unsigned long long)numberOfPixelsUsedByImage:(UIImage *)image {
381 @
synchronized(
self) {
386 return (
unsigned long long)(image.size.width * image.size.height * [image scale] * [image scale]);
391 @
synchronized(
self) {
392 [
super removeAllObjects];
394 self.numberOfPixels = 0;
399 @
synchronized(
self) {
401 [
super reduceMemoryUsage];
406 NIMemoryCacheInfo* info = [
self.lruCacheObjects firstObject];
407 [
self removeCacheInfoForName:info.name];
413 - (BOOL)shouldSetObject:(
id)object withName:(NSString *)name previousObject:(
id)previousObject {
414 @
synchronized(
self) {
415 NIDASSERT(nil ==
object || [
object isKindOfClass:[UIImage
class]]);
416 if (![
object isKindOfClass:[UIImage
class]]) {
420 _numberOfPixels -= [self numberOfPixelsUsedByImage:previousObject];
421 _numberOfPixels += [self numberOfPixelsUsedByImage:object];
427 - (void)didSetObject:(
id)object withName:(NSString *)name {
428 @
synchronized(
self) {
434 NIMemoryCacheInfo* info = [
self.lruCacheObjects firstObject];
435 [
self removeCacheInfoForName:info.name];
441 - (void)willRemoveObject:(
id)object withName:(NSString *)name {
442 @
synchronized(
self) {
443 NIDASSERT(nil ==
object || [
object isKindOfClass:[UIImage
class]]);
444 if (nil ==
object || ![
object isKindOfClass:[UIImage
class]]) {
448 self.numberOfPixels -= [self numberOfPixelsUsedByImage:object];
void removeAllObjects()
Removes all objects from the cache, regardless of expiration dates.
An in-memory cache for storing objects with expiration support.
void reduceMemoryUsage()
Removes all expired objects from the cache.
void storeObject:withName:expiresAfter:(id object,[withName] NSString *name,[expiresAfter] NSDate *expirationDate)
Stores an object in the cache with an expiration date.
BOOL shouldSetObject:withName:previousObject:(id object,[withName] NSString *name,[previousObject] id previousObject)
An object is about to be stored in the cache.
An in-memory cache for storing images with caps on the total number of pixels.
unsigned long long numberOfPixels
Returns the total number of pixels being stored in the cache.
NSString * nameOfMostRecentlyUsedObject()
Retrieve the key with the most fresh access.
unsigned long long maxNumberOfPixelsUnderStress
The maximum number of pixels this cache may store after a call to reduceMemoryUsage.
void removeObjectWithName:(NSString *name)
Removes an object from the cache with the given name.
NSUInteger count()
Returns the number of objects currently in the cache.
unsigned long long maxNumberOfPixels
The maximum number of pixels this cache may ever store.
NSString * nameOfLeastRecentlyUsedObject()
Retrieve the name of the object that was least recently used.