2015-07-13 15:42:32 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Adapted from https://github.com/nicklockwood/OSCache
|
|
|
|
|
|
|
|
#import "RCTCache.h"
|
|
|
|
|
|
|
|
#import "RCTAssert.h"
|
|
|
|
|
|
|
|
#import <TargetConditionals.h>
|
|
|
|
#if TARGET_OS_IPHONE
|
|
|
|
#import <UIKit/UIKit.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
@interface RCTCacheEntry : NSObject
|
|
|
|
|
|
|
|
@property (nonatomic, strong) NSObject *object;
|
|
|
|
@property (nonatomic, assign) NSUInteger cost;
|
|
|
|
@property (nonatomic, assign) NSInteger sequenceNumber;
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation RCTCacheEntry
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@interface RCTCache_Private : NSObject
|
|
|
|
|
|
|
|
@property (nonatomic, unsafe_unretained) id<RCTCacheDelegate> delegate;
|
|
|
|
@property (nonatomic, assign) NSUInteger countLimit;
|
|
|
|
@property (nonatomic, assign) NSUInteger totalCostLimit;
|
|
|
|
@property (nonatomic, copy) NSString *name;
|
|
|
|
|
|
|
|
@property (nonatomic, strong) NSMutableDictionary *cache;
|
2015-07-14 11:53:54 +00:00
|
|
|
@property (nonatomic, assign) NSUInteger totalCost;
|
2015-07-13 15:42:32 +00:00
|
|
|
@property (nonatomic, assign) NSInteger sequenceNumber;
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation RCTCache_Private
|
2015-07-14 11:53:54 +00:00
|
|
|
{
|
|
|
|
BOOL _delegateRespondsToWillEvictObject;
|
|
|
|
BOOL _delegateRespondsToShouldEvictObject;
|
|
|
|
BOOL _currentlyCleaning;
|
|
|
|
NSMutableArray *_entryPool;
|
|
|
|
NSLock *_lock;
|
|
|
|
}
|
2015-07-13 15:42:32 +00:00
|
|
|
|
2015-08-24 10:14:33 +00:00
|
|
|
- (instancetype)init
|
2015-07-13 15:42:32 +00:00
|
|
|
{
|
|
|
|
if ((self = [super init]))
|
|
|
|
{
|
|
|
|
//create storage
|
2015-08-17 14:35:34 +00:00
|
|
|
_cache = [NSMutableDictionary new];
|
|
|
|
_entryPool = [NSMutableArray new];
|
|
|
|
_lock = [NSLock new];
|
2015-07-13 15:42:32 +00:00
|
|
|
_totalCost = 0;
|
|
|
|
|
|
|
|
#if TARGET_OS_IPHONE
|
|
|
|
|
|
|
|
//clean up in the event of a memory warning
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanUpAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setDelegate:(id<RCTCacheDelegate>)delegate
|
|
|
|
{
|
|
|
|
_delegate = delegate;
|
|
|
|
_delegateRespondsToShouldEvictObject = [delegate respondsToSelector:@selector(cache:shouldEvictObject:)];
|
|
|
|
_delegateRespondsToWillEvictObject = [delegate respondsToSelector:@selector(cache:willEvictObject:)];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setCountLimit:(NSUInteger)countLimit
|
|
|
|
{
|
|
|
|
[_lock lock];
|
|
|
|
_countLimit = countLimit;
|
|
|
|
[_lock unlock];
|
2015-07-14 11:53:54 +00:00
|
|
|
[self cleanUp:NO];
|
2015-07-13 15:42:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setTotalCostLimit:(NSUInteger)totalCostLimit
|
|
|
|
{
|
|
|
|
[_lock lock];
|
|
|
|
_totalCostLimit = totalCostLimit;
|
|
|
|
[_lock unlock];
|
2015-07-14 11:53:54 +00:00
|
|
|
[self cleanUp:NO];
|
2015-07-13 15:42:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (NSUInteger)count
|
|
|
|
{
|
2015-08-24 10:14:33 +00:00
|
|
|
return _cache.count;
|
2015-07-13 15:42:32 +00:00
|
|
|
}
|
|
|
|
|
2015-07-14 11:53:54 +00:00
|
|
|
- (void)cleanUp:(BOOL)keepEntries
|
2015-07-13 15:42:32 +00:00
|
|
|
{
|
|
|
|
[_lock lock];
|
2015-07-14 11:53:54 +00:00
|
|
|
NSUInteger maxCount = _countLimit ?: INT_MAX;
|
|
|
|
NSUInteger maxCost = _totalCostLimit ?: INT_MAX;
|
|
|
|
NSUInteger totalCount = _cache.count;
|
|
|
|
NSMutableArray *keys = [_cache.allKeys mutableCopy];
|
|
|
|
while (totalCount > maxCount || _totalCost > maxCost)
|
2015-07-13 15:42:32 +00:00
|
|
|
{
|
2015-07-14 11:53:54 +00:00
|
|
|
NSInteger lowestSequenceNumber = INT_MAX;
|
|
|
|
RCTCacheEntry *lowestEntry = nil;
|
|
|
|
id lowestKey = nil;
|
2015-07-13 15:42:32 +00:00
|
|
|
|
|
|
|
//remove oldest items until within limit
|
|
|
|
for (id key in keys)
|
|
|
|
{
|
2015-07-14 11:53:54 +00:00
|
|
|
RCTCacheEntry *entry = _cache[key];
|
|
|
|
if (entry.sequenceNumber < lowestSequenceNumber)
|
2015-07-13 15:42:32 +00:00
|
|
|
{
|
2015-07-14 11:53:54 +00:00
|
|
|
lowestSequenceNumber = entry.sequenceNumber;
|
|
|
|
lowestEntry = entry;
|
|
|
|
lowestKey = key;
|
2015-07-13 15:42:32 +00:00
|
|
|
}
|
2015-07-14 11:53:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (lowestKey)
|
|
|
|
{
|
|
|
|
[keys removeObject:lowestKey];
|
|
|
|
if (!_delegateRespondsToShouldEvictObject ||
|
|
|
|
[_delegate cache:(RCTCache *)self shouldEvictObject:lowestEntry.object])
|
2015-07-13 15:42:32 +00:00
|
|
|
{
|
|
|
|
if (_delegateRespondsToWillEvictObject)
|
|
|
|
{
|
|
|
|
_currentlyCleaning = YES;
|
2015-07-14 11:53:54 +00:00
|
|
|
[self.delegate cache:(RCTCache *)self willEvictObject:lowestEntry.object];
|
2015-07-13 15:42:32 +00:00
|
|
|
_currentlyCleaning = NO;
|
|
|
|
}
|
2015-07-14 11:53:54 +00:00
|
|
|
[_cache removeObjectForKey:lowestKey];
|
|
|
|
_totalCost -= lowestEntry.cost;
|
2015-07-13 15:42:32 +00:00
|
|
|
totalCount --;
|
2015-07-14 11:53:54 +00:00
|
|
|
if (keepEntries)
|
|
|
|
{
|
|
|
|
[_entryPool addObject:lowestEntry];
|
|
|
|
lowestEntry.object = nil;
|
|
|
|
}
|
2015-07-13 15:42:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
[_lock unlock];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)cleanUpAllObjects
|
|
|
|
{
|
|
|
|
[_lock lock];
|
|
|
|
if (_delegateRespondsToShouldEvictObject || _delegateRespondsToWillEvictObject)
|
|
|
|
{
|
2015-08-24 10:14:33 +00:00
|
|
|
NSArray *keys = _cache.allKeys;
|
2015-07-13 15:42:32 +00:00
|
|
|
if (_delegateRespondsToShouldEvictObject)
|
|
|
|
{
|
|
|
|
//sort, oldest first (in case we want to use that information in our eviction test)
|
|
|
|
keys = [keys sortedArrayUsingComparator:^NSComparisonResult(id key1, id key2) {
|
2015-07-14 11:53:54 +00:00
|
|
|
RCTCacheEntry *entry1 = self->_cache[key1];
|
|
|
|
RCTCacheEntry *entry2 = self->_cache[key2];
|
2015-07-13 15:42:32 +00:00
|
|
|
return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber));
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
//remove all items individually
|
|
|
|
for (id key in keys)
|
|
|
|
{
|
|
|
|
RCTCacheEntry *entry = _cache[key];
|
2015-07-14 11:53:54 +00:00
|
|
|
if (!_delegateRespondsToShouldEvictObject || [_delegate cache:(RCTCache *)self shouldEvictObject:entry.object])
|
2015-07-13 15:42:32 +00:00
|
|
|
{
|
|
|
|
if (_delegateRespondsToWillEvictObject)
|
|
|
|
{
|
|
|
|
_currentlyCleaning = YES;
|
2015-07-14 11:53:54 +00:00
|
|
|
[_delegate cache:(RCTCache *)self willEvictObject:entry.object];
|
2015-07-13 15:42:32 +00:00
|
|
|
_currentlyCleaning = NO;
|
|
|
|
}
|
|
|
|
[_cache removeObjectForKey:key];
|
|
|
|
_totalCost -= entry.cost;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_totalCost = 0;
|
|
|
|
[_cache removeAllObjects];
|
|
|
|
_sequenceNumber = 0;
|
|
|
|
}
|
|
|
|
[_lock unlock];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)resequence
|
|
|
|
{
|
|
|
|
//sort, oldest first
|
2015-08-24 10:14:33 +00:00
|
|
|
NSArray *entries = [_cache.allValues sortedArrayUsingComparator:^NSComparisonResult(RCTCacheEntry *entry1, RCTCacheEntry *entry2) {
|
2015-07-13 15:42:32 +00:00
|
|
|
return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber));
|
|
|
|
}];
|
|
|
|
|
|
|
|
//renumber items
|
|
|
|
NSInteger index = 0;
|
|
|
|
for (RCTCacheEntry *entry in entries)
|
|
|
|
{
|
|
|
|
entry.sequenceNumber = index++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-14 11:53:54 +00:00
|
|
|
- (id)objectForKey:(id)key
|
2015-07-13 15:42:32 +00:00
|
|
|
{
|
|
|
|
[_lock lock];
|
|
|
|
RCTCacheEntry *entry = _cache[key];
|
|
|
|
entry.sequenceNumber = _sequenceNumber++;
|
|
|
|
if (_sequenceNumber < 0)
|
|
|
|
{
|
|
|
|
[self resequence];
|
|
|
|
}
|
|
|
|
id object = entry.object;
|
|
|
|
[_lock unlock];
|
|
|
|
return object;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id)objectForKeyedSubscript:(id<NSCopying>)key
|
|
|
|
{
|
|
|
|
return [self objectForKey:key];
|
|
|
|
}
|
|
|
|
|
2015-07-14 11:53:54 +00:00
|
|
|
- (void)setObject:(id)obj forKey:(id)key
|
2015-07-13 15:42:32 +00:00
|
|
|
{
|
|
|
|
[self setObject:obj forKey:key cost:0];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key
|
|
|
|
{
|
|
|
|
[self setObject:obj forKey:key cost:0];
|
|
|
|
}
|
|
|
|
|
2015-07-14 11:53:54 +00:00
|
|
|
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g
|
2015-07-13 15:42:32 +00:00
|
|
|
{
|
2015-07-14 11:53:54 +00:00
|
|
|
if (!obj)
|
|
|
|
{
|
|
|
|
[self removeObjectForKey:key];
|
|
|
|
return;
|
|
|
|
}
|
2015-07-13 15:42:32 +00:00
|
|
|
RCTAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
|
|
|
|
[_lock lock];
|
|
|
|
_totalCost -= [_cache[key] cost];
|
|
|
|
_totalCost += g;
|
2015-07-14 11:53:54 +00:00
|
|
|
RCTCacheEntry *entry = _cache[key];
|
|
|
|
if (!entry) {
|
2015-08-17 14:35:34 +00:00
|
|
|
entry = [RCTCacheEntry new];
|
2015-07-14 11:53:54 +00:00
|
|
|
_cache[key] = entry;
|
|
|
|
}
|
|
|
|
entry.object = obj;
|
|
|
|
entry.cost = g;
|
|
|
|
entry.sequenceNumber = _sequenceNumber++;
|
2015-07-13 15:42:32 +00:00
|
|
|
if (_sequenceNumber < 0)
|
|
|
|
{
|
|
|
|
[self resequence];
|
|
|
|
}
|
|
|
|
[_lock unlock];
|
2015-07-14 11:53:54 +00:00
|
|
|
[self cleanUp:YES];
|
2015-07-13 15:42:32 +00:00
|
|
|
}
|
|
|
|
|
2015-07-14 11:53:54 +00:00
|
|
|
- (void)removeObjectForKey:(id)key
|
2015-07-13 15:42:32 +00:00
|
|
|
{
|
|
|
|
RCTAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
|
|
|
|
[_lock lock];
|
2015-07-14 11:53:54 +00:00
|
|
|
RCTCacheEntry *entry = _cache[key];
|
|
|
|
if (entry) {
|
|
|
|
_totalCost -= entry.cost;
|
|
|
|
entry.object = nil;
|
|
|
|
[_entryPool addObject:entry];
|
|
|
|
[_cache removeObjectForKey:key];
|
|
|
|
}
|
2015-07-13 15:42:32 +00:00
|
|
|
[_lock unlock];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)removeAllObjects
|
|
|
|
{
|
|
|
|
RCTAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
|
|
|
|
[_lock lock];
|
|
|
|
_totalCost = 0;
|
|
|
|
_sequenceNumber = 0;
|
2015-07-14 11:53:54 +00:00
|
|
|
for (RCTCacheEntry *entry in _cache.allValues)
|
|
|
|
{
|
|
|
|
entry.object = nil;
|
|
|
|
[_entryPool addObject:entry];
|
|
|
|
}
|
2015-07-13 15:42:32 +00:00
|
|
|
[_cache removeAllObjects];
|
|
|
|
[_lock unlock];
|
|
|
|
}
|
|
|
|
|
2015-07-14 11:53:54 +00:00
|
|
|
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
|
|
|
|
objects:(id __unsafe_unretained [])buffer
|
|
|
|
count:(NSUInteger)len
|
|
|
|
{
|
|
|
|
[_lock lock];
|
|
|
|
NSUInteger count = [_cache countByEnumeratingWithState:state objects:buffer count:len];
|
|
|
|
[_lock unlock];
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)enumerateKeysAndObjectsUsingBlock:(void (^)(id key, id obj, BOOL *stop))block
|
|
|
|
{
|
|
|
|
[_lock lock];
|
|
|
|
[_cache enumerateKeysAndObjectsUsingBlock:block];
|
|
|
|
[_lock unlock];
|
|
|
|
}
|
|
|
|
|
2015-07-13 15:42:32 +00:00
|
|
|
//handle unimplemented methods
|
|
|
|
|
2015-07-14 11:53:54 +00:00
|
|
|
- (BOOL)isKindOfClass:(Class)aClass
|
2015-07-13 15:42:32 +00:00
|
|
|
{
|
|
|
|
//pretend that we're an RCTCache if anyone asks
|
2015-07-14 11:53:54 +00:00
|
|
|
if (aClass == [RCTCache class] || aClass == [NSCache class])
|
2015-07-13 15:42:32 +00:00
|
|
|
{
|
|
|
|
return YES;
|
|
|
|
}
|
2015-07-14 11:53:54 +00:00
|
|
|
return [super isKindOfClass:aClass];
|
2015-07-13 15:42:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
|
|
|
|
{
|
|
|
|
//protect against calls to unimplemented NSCache methods
|
|
|
|
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
|
|
|
|
if (!signature)
|
|
|
|
{
|
|
|
|
signature = [NSCache instanceMethodSignatureForSelector:selector];
|
|
|
|
}
|
|
|
|
return signature;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)forwardInvocation:(NSInvocation *)invocation
|
|
|
|
{
|
|
|
|
#pragma clang diagnostic push
|
|
|
|
#pragma clang diagnostic ignored "-Wnonnull"
|
|
|
|
|
|
|
|
[invocation invokeWithTarget:nil];
|
|
|
|
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation RCTCache
|
|
|
|
|
2015-07-14 11:53:54 +00:00
|
|
|
+ (id)alloc
|
2015-07-13 15:42:32 +00:00
|
|
|
{
|
|
|
|
return (RCTCache *)[RCTCache_Private alloc];
|
|
|
|
}
|
|
|
|
|
2015-07-14 11:53:54 +00:00
|
|
|
- (id)objectForKeyedSubscript:(__unused id<NSCopying>)key { return nil; }
|
2015-07-13 15:42:32 +00:00
|
|
|
- (void)setObject:(__unused id)obj forKeyedSubscript:(__unused id<NSCopying>)key {}
|
2015-07-14 11:53:54 +00:00
|
|
|
- (void)enumerateKeysAndObjectsUsingBlock:(__unused void (^)(id, id, BOOL *))block { }
|
|
|
|
- (NSUInteger)countByEnumeratingWithState:(__unused NSFastEnumerationState *)state
|
|
|
|
objects:(__unused __unsafe_unretained id [])buffer
|
|
|
|
count:(__unused NSUInteger)len { return 0; }
|
2015-07-13 15:42:32 +00:00
|
|
|
|
|
|
|
@end
|