NewsBlur-viq/clients/ios/Other Sources/PINCache/PINMemoryCache.m

756 lines
19 KiB
Objective-C
Executable file

// PINCache is a modified version of TMCache
// Modifications by Garrett Moon
// Copyright (c) 2015 Pinterest. All rights reserved.
#import "PINMemoryCache.h"
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0
#import <UIKit/UIKit.h>
#endif
static NSString * const PINMemoryCachePrefix = @"com.pinterest.PINMemoryCache";
@interface PINMemoryCache ()
#if OS_OBJECT_USE_OBJC
@property (strong, nonatomic) dispatch_queue_t concurrentQueue;
@property (strong, nonatomic) dispatch_semaphore_t lockSemaphore;
#else
@property (assign, nonatomic) dispatch_queue_t concurrentQueue;
@property (assign, nonatomic) dispatch_semaphore_t lockSemaphore;
#endif
@property (strong, nonatomic) NSMutableDictionary *dictionary;
@property (strong, nonatomic) NSMutableDictionary *dates;
@property (strong, nonatomic) NSMutableDictionary *costs;
@end
@implementation PINMemoryCache
@synthesize ageLimit = _ageLimit;
@synthesize costLimit = _costLimit;
@synthesize totalCost = _totalCost;
@synthesize ttlCache = _ttlCache;
@synthesize willAddObjectBlock = _willAddObjectBlock;
@synthesize willRemoveObjectBlock = _willRemoveObjectBlock;
@synthesize willRemoveAllObjectsBlock = _willRemoveAllObjectsBlock;
@synthesize didAddObjectBlock = _didAddObjectBlock;
@synthesize didRemoveObjectBlock = _didRemoveObjectBlock;
@synthesize didRemoveAllObjectsBlock = _didRemoveAllObjectsBlock;
@synthesize didReceiveMemoryWarningBlock = _didReceiveMemoryWarningBlock;
@synthesize didEnterBackgroundBlock = _didEnterBackgroundBlock;
#pragma mark - Initialization -
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
#if !OS_OBJECT_USE_OBJC
dispatch_release(_concurrentQueue);
dispatch_release(_lockSemaphore);
_concurrentQueue = nil;
#endif
}
- (instancetype)init
{
if (self = [super init]) {
_lockSemaphore = dispatch_semaphore_create(1);
NSString *queueName = [[NSString alloc] initWithFormat:@"%@.%p", PINMemoryCachePrefix, (void *)self];
_concurrentQueue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_CONCURRENT);
_dictionary = [[NSMutableDictionary alloc] init];
_dates = [[NSMutableDictionary alloc] init];
_costs = [[NSMutableDictionary alloc] init];
_willAddObjectBlock = nil;
_willRemoveObjectBlock = nil;
_willRemoveAllObjectsBlock = nil;
_didAddObjectBlock = nil;
_didRemoveObjectBlock = nil;
_didRemoveAllObjectsBlock = nil;
_didReceiveMemoryWarningBlock = nil;
_didEnterBackgroundBlock = nil;
_ageLimit = 0.0;
_costLimit = 0;
_totalCost = 0;
_removeAllObjectsOnMemoryWarning = YES;
_removeAllObjectsOnEnteringBackground = YES;
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0 && !TARGET_OS_WATCH
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveEnterBackgroundNotification:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarningNotification:)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
#endif
}
return self;
}
+ (instancetype)sharedCache
{
static id cache;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
cache = [[self alloc] init];
});
return cache;
}
#pragma mark - Private Methods -
- (void)didReceiveMemoryWarningNotification:(NSNotification *)notification {
if (self.removeAllObjectsOnMemoryWarning)
[self removeAllObjects:nil];
__weak PINMemoryCache *weakSelf = self;
dispatch_async(_concurrentQueue, ^{
PINMemoryCache *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
[strongSelf lock];
PINMemoryCacheBlock didReceiveMemoryWarningBlock = strongSelf->_didReceiveMemoryWarningBlock;
[strongSelf unlock];
if (didReceiveMemoryWarningBlock)
didReceiveMemoryWarningBlock(strongSelf);
});
}
- (void)didReceiveEnterBackgroundNotification:(NSNotification *)notification
{
if (self.removeAllObjectsOnEnteringBackground)
[self removeAllObjects:nil];
__weak PINMemoryCache *weakSelf = self;
dispatch_async(_concurrentQueue, ^{
PINMemoryCache *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
[strongSelf lock];
PINMemoryCacheBlock didEnterBackgroundBlock = strongSelf->_didEnterBackgroundBlock;
[strongSelf unlock];
if (didEnterBackgroundBlock)
didEnterBackgroundBlock(strongSelf);
});
}
- (void)removeObjectAndExecuteBlocksForKey:(NSString *)key
{
[self lock];
id object = _dictionary[key];
NSNumber *cost = _costs[key];
PINMemoryCacheObjectBlock willRemoveObjectBlock = _willRemoveObjectBlock;
PINMemoryCacheObjectBlock didRemoveObjectBlock = _didRemoveObjectBlock;
[self unlock];
if (willRemoveObjectBlock)
willRemoveObjectBlock(self, key, object);
[self lock];
if (cost)
_totalCost -= [cost unsignedIntegerValue];
[_dictionary removeObjectForKey:key];
[_dates removeObjectForKey:key];
[_costs removeObjectForKey:key];
[self unlock];
if (didRemoveObjectBlock)
didRemoveObjectBlock(self, key, nil);
}
- (void)trimMemoryToDate:(NSDate *)trimDate
{
[self lock];
NSArray *keysSortedByDate = [_dates keysSortedByValueUsingSelector:@selector(compare:)];
NSDictionary *dates = [_dates copy];
[self unlock];
for (NSString *key in keysSortedByDate) { // oldest objects first
NSDate *accessDate = dates[key];
if (!accessDate)
continue;
if ([accessDate compare:trimDate] == NSOrderedAscending) { // older than trim date
[self removeObjectAndExecuteBlocksForKey:key];
} else {
break;
}
}
}
- (void)trimToCostLimit:(NSUInteger)limit
{
NSUInteger totalCost = 0;
[self lock];
totalCost = _totalCost;
NSArray *keysSortedByCost = [_costs keysSortedByValueUsingSelector:@selector(compare:)];
[self unlock];
if (totalCost <= limit) {
return;
}
for (NSString *key in [keysSortedByCost reverseObjectEnumerator]) { // costliest objects first
[self removeObjectAndExecuteBlocksForKey:key];
[self lock];
totalCost = _totalCost;
[self unlock];
if (totalCost <= limit)
break;
}
}
- (void)trimToCostLimitByDate:(NSUInteger)limit
{
NSUInteger totalCost = 0;
[self lock];
totalCost = _totalCost;
NSArray *keysSortedByDate = [_dates keysSortedByValueUsingSelector:@selector(compare:)];
[self unlock];
if (totalCost <= limit)
return;
for (NSString *key in keysSortedByDate) { // oldest objects first
[self removeObjectAndExecuteBlocksForKey:key];
[self lock];
totalCost = _totalCost;
[self unlock];
if (totalCost <= limit)
break;
}
}
- (void)trimToAgeLimitRecursively
{
[self lock];
NSTimeInterval ageLimit = _ageLimit;
[self unlock];
if (ageLimit == 0.0)
return;
NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:-ageLimit];
[self trimMemoryToDate:date];
__weak PINMemoryCache *weakSelf = self;
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(ageLimit * NSEC_PER_SEC));
dispatch_after(time, _concurrentQueue, ^(void){
PINMemoryCache *strongSelf = weakSelf;
[strongSelf trimToAgeLimitRecursively];
});
}
#pragma mark - Public Asynchronous Methods -
- (void)containsObjectForKey:(NSString *)key block:(PINMemoryCacheContainmentBlock)block
{
if (!key || !block)
return;
__weak PINMemoryCache *weakSelf = self;
dispatch_async(_concurrentQueue, ^{
PINMemoryCache *strongSelf = weakSelf;
BOOL containsObject = [strongSelf containsObjectForKey:key];
block(containsObject);
});
}
- (void)objectForKey:(NSString *)key block:(PINMemoryCacheObjectBlock)block
{
__weak PINMemoryCache *weakSelf = self;
dispatch_async(_concurrentQueue, ^{
PINMemoryCache *strongSelf = weakSelf;
id object = [strongSelf objectForKey:key];
if (block)
block(strongSelf, key, object);
});
}
- (void)setObject:(id)object forKey:(NSString *)key block:(PINMemoryCacheObjectBlock)block
{
[self setObject:object forKey:key withCost:0 block:block];
}
- (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost block:(PINMemoryCacheObjectBlock)block
{
__weak PINMemoryCache *weakSelf = self;
dispatch_async(_concurrentQueue, ^{
PINMemoryCache *strongSelf = weakSelf;
[strongSelf setObject:object forKey:key withCost:cost];
if (block)
block(strongSelf, key, object);
});
}
- (void)removeObjectForKey:(NSString *)key block:(PINMemoryCacheObjectBlock)block
{
__weak PINMemoryCache *weakSelf = self;
dispatch_async(_concurrentQueue, ^{
PINMemoryCache *strongSelf = weakSelf;
[strongSelf removeObjectForKey:key];
if (block)
block(strongSelf, key, nil);
});
}
- (void)trimToDate:(NSDate *)trimDate block:(PINMemoryCacheBlock)block
{
__weak PINMemoryCache *weakSelf = self;
dispatch_async(_concurrentQueue, ^{
PINMemoryCache *strongSelf = weakSelf;
[strongSelf trimToDate:trimDate];
if (block)
block(strongSelf);
});
}
- (void)trimToCost:(NSUInteger)cost block:(PINMemoryCacheBlock)block
{
__weak PINMemoryCache *weakSelf = self;
dispatch_async(_concurrentQueue, ^{
PINMemoryCache *strongSelf = weakSelf;
[strongSelf trimToCost:cost];
if (block)
block(strongSelf);
});
}
- (void)trimToCostByDate:(NSUInteger)cost block:(PINMemoryCacheBlock)block
{
__weak PINMemoryCache *weakSelf = self;
dispatch_async(_concurrentQueue, ^{
PINMemoryCache *strongSelf = weakSelf;
[strongSelf trimToCostByDate:cost];
if (block)
block(strongSelf);
});
}
- (void)removeAllObjects:(PINMemoryCacheBlock)block
{
__weak PINMemoryCache *weakSelf = self;
dispatch_async(_concurrentQueue, ^{
PINMemoryCache *strongSelf = weakSelf;
[strongSelf removeAllObjects];
if (block)
block(strongSelf);
});
}
- (void)enumerateObjectsWithBlock:(PINMemoryCacheObjectBlock)block completionBlock:(PINMemoryCacheBlock)completionBlock
{
__weak PINMemoryCache *weakSelf = self;
dispatch_async(_concurrentQueue, ^{
PINMemoryCache *strongSelf = weakSelf;
[strongSelf enumerateObjectsWithBlock:block];
if (completionBlock)
completionBlock(strongSelf);
});
}
#pragma mark - Public Synchronous Methods -
- (BOOL)containsObjectForKey:(NSString *)key
{
if (!key)
return NO;
[self lock];
BOOL containsObject = (_dictionary[key] != nil);
[self unlock];
return containsObject;
}
- (__nullable id)objectForKey:(NSString *)key
{
if (!key)
return nil;
NSDate *now = [[NSDate alloc] init];
[self lock];
id object = nil;
// If the cache should behave like a TTL cache, then only fetch the object if there's a valid ageLimit and the object is still alive
if (!self->_ttlCache || self->_ageLimit <= 0 || fabs([[_dates objectForKey:key] timeIntervalSinceDate:now]) < self->_ageLimit) {
object = _dictionary[key];
}
[self unlock];
if (object) {
[self lock];
_dates[key] = now;
[self unlock];
}
return object;
}
- (id)objectForKeyedSubscript:(NSString *)key
{
return [self objectForKey:key];
}
- (void)setObject:(id)object forKey:(NSString *)key
{
[self setObject:object forKey:key withCost:0];
}
- (void)setObject:(id)object forKeyedSubscript:(NSString *)key
{
[self setObject:object forKey:key];
}
- (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost
{
if (!key || !object)
return;
[self lock];
PINMemoryCacheObjectBlock willAddObjectBlock = _willAddObjectBlock;
PINMemoryCacheObjectBlock didAddObjectBlock = _didAddObjectBlock;
NSUInteger costLimit = _costLimit;
[self unlock];
if (willAddObjectBlock)
willAddObjectBlock(self, key, object);
[self lock];
NSNumber* oldCost = _costs[key];
if (oldCost)
_totalCost -= [oldCost unsignedIntegerValue];
_dictionary[key] = object;
_dates[key] = [[NSDate alloc] init];
_costs[key] = @(cost);
_totalCost += cost;
[self unlock];
if (didAddObjectBlock)
didAddObjectBlock(self, key, object);
if (costLimit > 0)
[self trimToCostByDate:costLimit];
}
- (void)removeObjectForKey:(NSString *)key
{
if (!key)
return;
[self removeObjectAndExecuteBlocksForKey:key];
}
- (void)trimToDate:(NSDate *)trimDate
{
if (!trimDate)
return;
if ([trimDate isEqualToDate:[NSDate distantPast]]) {
[self removeAllObjects];
return;
}
[self trimMemoryToDate:trimDate];
}
- (void)trimToCost:(NSUInteger)cost
{
[self trimToCostLimit:cost];
}
- (void)trimToCostByDate:(NSUInteger)cost
{
[self trimToCostLimitByDate:cost];
}
- (void)removeAllObjects
{
[self lock];
PINMemoryCacheBlock willRemoveAllObjectsBlock = _willRemoveAllObjectsBlock;
PINMemoryCacheBlock didRemoveAllObjectsBlock = _didRemoveAllObjectsBlock;
[self unlock];
if (willRemoveAllObjectsBlock)
willRemoveAllObjectsBlock(self);
[self lock];
[_dictionary removeAllObjects];
[_dates removeAllObjects];
[_costs removeAllObjects];
_totalCost = 0;
[self unlock];
if (didRemoveAllObjectsBlock)
didRemoveAllObjectsBlock(self);
}
- (void)enumerateObjectsWithBlock:(PINMemoryCacheObjectBlock)block
{
if (!block)
return;
[self lock];
NSDate *now = [[NSDate alloc] init];
NSArray *keysSortedByDate = [_dates keysSortedByValueUsingSelector:@selector(compare:)];
for (NSString *key in keysSortedByDate) {
// If the cache should behave like a TTL cache, then only fetch the object if there's a valid ageLimit and the object is still alive
if (!self->_ttlCache || self->_ageLimit <= 0 || fabs([[_dates objectForKey:key] timeIntervalSinceDate:now]) < self->_ageLimit) {
block(self, key, _dictionary[key]);
}
}
[self unlock];
}
#pragma mark - Public Thread Safe Accessors -
- (PINMemoryCacheObjectBlock)willAddObjectBlock
{
[self lock];
PINMemoryCacheObjectBlock block = _willAddObjectBlock;
[self unlock];
return block;
}
- (void)setWillAddObjectBlock:(PINMemoryCacheObjectBlock)block
{
[self lock];
_willAddObjectBlock = [block copy];
[self unlock];
}
- (PINMemoryCacheObjectBlock)willRemoveObjectBlock
{
[self lock];
PINMemoryCacheObjectBlock block = _willRemoveObjectBlock;
[self unlock];
return block;
}
- (void)setWillRemoveObjectBlock:(PINMemoryCacheObjectBlock)block
{
[self lock];
_willRemoveObjectBlock = [block copy];
[self unlock];
}
- (PINMemoryCacheBlock)willRemoveAllObjectsBlock
{
[self lock];
PINMemoryCacheBlock block = _willRemoveAllObjectsBlock;
[self unlock];
return block;
}
- (void)setWillRemoveAllObjectsBlock:(PINMemoryCacheBlock)block
{
[self lock];
_willRemoveAllObjectsBlock = [block copy];
[self unlock];
}
- (PINMemoryCacheObjectBlock)didAddObjectBlock
{
[self lock];
PINMemoryCacheObjectBlock block = _didAddObjectBlock;
[self unlock];
return block;
}
- (void)setDidAddObjectBlock:(PINMemoryCacheObjectBlock)block
{
[self lock];
_didAddObjectBlock = [block copy];
[self unlock];
}
- (PINMemoryCacheObjectBlock)didRemoveObjectBlock
{
[self lock];
PINMemoryCacheObjectBlock block = _didRemoveObjectBlock;
[self unlock];
return block;
}
- (void)setDidRemoveObjectBlock:(PINMemoryCacheObjectBlock)block
{
[self lock];
_didRemoveObjectBlock = [block copy];
[self unlock];
}
- (PINMemoryCacheBlock)didRemoveAllObjectsBlock
{
[self lock];
PINMemoryCacheBlock block = _didRemoveAllObjectsBlock;
[self unlock];
return block;
}
- (void)setDidRemoveAllObjectsBlock:(PINMemoryCacheBlock)block
{
[self lock];
_didRemoveAllObjectsBlock = [block copy];
[self unlock];
}
- (PINMemoryCacheBlock)didReceiveMemoryWarningBlock
{
[self lock];
PINMemoryCacheBlock block = _didReceiveMemoryWarningBlock;
[self unlock];
return block;
}
- (void)setDidReceiveMemoryWarningBlock:(PINMemoryCacheBlock)block
{
[self lock];
_didReceiveMemoryWarningBlock = [block copy];
[self unlock];
}
- (PINMemoryCacheBlock)didEnterBackgroundBlock
{
[self lock];
PINMemoryCacheBlock block = _didEnterBackgroundBlock;
[self unlock];
return block;
}
- (void)setDidEnterBackgroundBlock:(PINMemoryCacheBlock)block
{
[self lock];
_didEnterBackgroundBlock = [block copy];
[self unlock];
}
- (NSTimeInterval)ageLimit
{
[self lock];
NSTimeInterval ageLimit = _ageLimit;
[self unlock];
return ageLimit;
}
- (void)setAgeLimit:(NSTimeInterval)ageLimit
{
[self lock];
_ageLimit = ageLimit;
[self unlock];
[self trimToAgeLimitRecursively];
}
- (NSUInteger)costLimit
{
[self lock];
NSUInteger costLimit = _costLimit;
[self unlock];
return costLimit;
}
- (void)setCostLimit:(NSUInteger)costLimit
{
[self lock];
_costLimit = costLimit;
[self unlock];
if (costLimit > 0)
[self trimToCostLimitByDate:costLimit];
}
- (NSUInteger)totalCost
{
[self lock];
NSUInteger cost = _totalCost;
[self unlock];
return cost;
}
- (BOOL)isTTLCache {
BOOL isTTLCache;
[self lock];
isTTLCache = _ttlCache;
[self unlock];
return isTTLCache;
}
- (void)setTtlCache:(BOOL)ttlCache {
[self lock];
_ttlCache = ttlCache;
[self unlock];
}
- (void)lock
{
dispatch_semaphore_wait(_lockSemaphore, DISPATCH_TIME_FOREVER);
}
- (void)unlock
{
dispatch_semaphore_signal(_lockSemaphore);
}
@end