mirror of
https://github.com/SoPat712/YTLitePlus.git
synced 2025-11-03 04:49:28 -05:00
410 lines
11 KiB
Objective-C
410 lines
11 KiB
Objective-C
//
|
|
// OSCache.m
|
|
//
|
|
// Version 1.2.1
|
|
//
|
|
// Created by Nick Lockwood on 01/01/2014.
|
|
// Copyright (C) 2014 Charcoal Design
|
|
//
|
|
// Distributed under the permissive zlib License
|
|
// Get the latest version from here:
|
|
//
|
|
// https://github.com/nicklockwood/OSCache
|
|
//
|
|
// This software is provided 'as-is', without any express or implied
|
|
// warranty. In no event will the authors be held liable for any damages
|
|
// arising from the use of this software.
|
|
//
|
|
// Permission is granted to anyone to use this software for any purpose,
|
|
// including commercial applications, and to alter it and redistribute it
|
|
// freely, subject to the following restrictions:
|
|
//
|
|
// 1. The origin of this software must not be misrepresented; you must not
|
|
// claim that you wrote the original software. If you use this software
|
|
// in a product, an acknowledgment in the product documentation would be
|
|
// appreciated but is not required.
|
|
//
|
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
|
// misrepresented as being the original software.
|
|
//
|
|
// 3. This notice may not be removed or altered from any source distribution.
|
|
//
|
|
|
|
#import "OSCache.h"
|
|
#import <TargetConditionals.h>
|
|
#if TARGET_OS_IPHONE
|
|
#import <UIKit/UIKit.h>
|
|
#endif
|
|
|
|
|
|
#import <Availability.h>
|
|
#if !__has_feature(objc_arc)
|
|
#error This class requires automatic reference counting
|
|
#endif
|
|
|
|
|
|
#pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis"
|
|
#pragma GCC diagnostic ignored "-Wdirect-ivar-access"
|
|
#pragma GCC diagnostic ignored "-Wgnu"
|
|
|
|
|
|
@interface OSCacheEntry : NSObject
|
|
|
|
@property (nonatomic, strong) NSObject *object;
|
|
@property (nonatomic, assign) NSUInteger cost;
|
|
@property (nonatomic, assign) NSInteger sequenceNumber;
|
|
|
|
@end
|
|
|
|
|
|
@implementation OSCacheEntry
|
|
|
|
@end
|
|
|
|
|
|
@interface OSCache_Private : NSObject
|
|
|
|
@property (nonatomic, unsafe_unretained) id<OSCacheDelegate> delegate;
|
|
@property (nonatomic, assign) NSUInteger countLimit;
|
|
@property (nonatomic, assign) NSUInteger totalCostLimit;
|
|
@property (nonatomic, copy) NSString *name;
|
|
|
|
@property (nonatomic, strong) NSMutableDictionary *cache;
|
|
@property (nonatomic, assign) NSUInteger totalCost;
|
|
@property (nonatomic, assign) NSInteger sequenceNumber;
|
|
|
|
@end
|
|
|
|
|
|
@implementation OSCache_Private
|
|
{
|
|
BOOL _delegateRespondsToWillEvictObject;
|
|
BOOL _delegateRespondsToShouldEvictObject;
|
|
BOOL _currentlyCleaning;
|
|
NSMutableArray *_entryPool;
|
|
NSLock *_lock;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
if ((self = [super init]))
|
|
{
|
|
//create storage
|
|
_cache = [[NSMutableDictionary alloc] init];
|
|
_entryPool = [[NSMutableArray alloc] init];
|
|
_lock = [[NSLock alloc] init];
|
|
_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<OSCacheDelegate>)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];
|
|
[self cleanUp:NO];
|
|
}
|
|
|
|
- (void)setTotalCostLimit:(NSUInteger)totalCostLimit
|
|
{
|
|
[_lock lock];
|
|
_totalCostLimit = totalCostLimit;
|
|
[_lock unlock];
|
|
[self cleanUp:NO];
|
|
}
|
|
|
|
- (NSUInteger)count
|
|
{
|
|
return [_cache count];
|
|
}
|
|
|
|
- (void)cleanUp:(BOOL)keepEntries
|
|
{
|
|
[_lock lock];
|
|
NSUInteger maxCount = _countLimit ?: INT_MAX;
|
|
NSUInteger maxCost = _totalCostLimit ?: INT_MAX;
|
|
NSUInteger totalCount = _cache.count;
|
|
NSMutableArray *keys = [_cache.allKeys mutableCopy];
|
|
while (totalCount > maxCount || _totalCost > maxCost)
|
|
{
|
|
NSInteger lowestSequenceNumber = INT_MAX;
|
|
OSCacheEntry *lowestEntry = nil;
|
|
id lowestKey = nil;
|
|
|
|
//remove oldest items until within limit
|
|
for (id key in keys)
|
|
{
|
|
OSCacheEntry *entry = _cache[key];
|
|
if (entry.sequenceNumber < lowestSequenceNumber)
|
|
{
|
|
lowestSequenceNumber = entry.sequenceNumber;
|
|
lowestEntry = entry;
|
|
lowestKey = key;
|
|
}
|
|
}
|
|
|
|
if (lowestKey)
|
|
{
|
|
[keys removeObject:lowestKey];
|
|
if (!_delegateRespondsToShouldEvictObject ||
|
|
[_delegate cache:(OSCache *)self shouldEvictObject:lowestEntry.object])
|
|
{
|
|
if (_delegateRespondsToWillEvictObject)
|
|
{
|
|
_currentlyCleaning = YES;
|
|
[self.delegate cache:(OSCache *)self willEvictObject:lowestEntry.object];
|
|
_currentlyCleaning = NO;
|
|
}
|
|
[_cache removeObjectForKey:lowestKey];
|
|
_totalCost -= lowestEntry.cost;
|
|
totalCount --;
|
|
if (keepEntries)
|
|
{
|
|
[_entryPool addObject:lowestEntry];
|
|
lowestEntry.object = nil;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
[_lock unlock];
|
|
}
|
|
|
|
- (void)cleanUpAllObjects
|
|
{
|
|
[_lock lock];
|
|
if (_delegateRespondsToShouldEvictObject || _delegateRespondsToWillEvictObject)
|
|
{
|
|
NSArray *keys = [_cache allKeys];
|
|
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) {
|
|
OSCacheEntry *entry1 = self->_cache[key1];
|
|
OSCacheEntry *entry2 = self->_cache[key2];
|
|
return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber));
|
|
}];
|
|
}
|
|
|
|
//remove all items individually
|
|
for (id key in keys)
|
|
{
|
|
OSCacheEntry *entry = _cache[key];
|
|
if (!_delegateRespondsToShouldEvictObject || [_delegate cache:(OSCache *)self shouldEvictObject:entry.object])
|
|
{
|
|
if (_delegateRespondsToWillEvictObject)
|
|
{
|
|
_currentlyCleaning = YES;
|
|
[_delegate cache:(OSCache *)self willEvictObject:entry.object];
|
|
_currentlyCleaning = NO;
|
|
}
|
|
[_cache removeObjectForKey:key];
|
|
_totalCost -= entry.cost;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_totalCost = 0;
|
|
[_cache removeAllObjects];
|
|
_sequenceNumber = 0;
|
|
}
|
|
[_lock unlock];
|
|
}
|
|
|
|
- (void)resequence
|
|
{
|
|
//sort, oldest first
|
|
NSArray *entries = [[_cache allValues] sortedArrayUsingComparator:^NSComparisonResult(OSCacheEntry *entry1, OSCacheEntry *entry2) {
|
|
return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber));
|
|
}];
|
|
|
|
//renumber items
|
|
NSInteger index = 0;
|
|
for (OSCacheEntry *entry in entries)
|
|
{
|
|
entry.sequenceNumber = index++;
|
|
}
|
|
}
|
|
|
|
- (id)objectForKey:(id)key
|
|
{
|
|
[_lock lock];
|
|
OSCacheEntry *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];
|
|
}
|
|
|
|
- (void)setObject:(id)obj forKey:(id)key
|
|
{
|
|
[self setObject:obj forKey:key cost:0];
|
|
}
|
|
|
|
- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key
|
|
{
|
|
[self setObject:obj forKey:key cost:0];
|
|
}
|
|
|
|
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g
|
|
{
|
|
if (!obj)
|
|
{
|
|
[self removeObjectForKey:key];
|
|
return;
|
|
}
|
|
NSAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
|
|
[_lock lock];
|
|
_totalCost -= [_cache[key] cost];
|
|
_totalCost += g;
|
|
OSCacheEntry *entry = _cache[key];
|
|
if (!entry) {
|
|
entry = [[OSCacheEntry alloc] init];
|
|
_cache[key] = entry;
|
|
}
|
|
entry.object = obj;
|
|
entry.cost = g;
|
|
entry.sequenceNumber = _sequenceNumber++;
|
|
if (_sequenceNumber < 0)
|
|
{
|
|
[self resequence];
|
|
}
|
|
[_lock unlock];
|
|
[self cleanUp:YES];
|
|
}
|
|
|
|
- (void)removeObjectForKey:(id)key
|
|
{
|
|
NSAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
|
|
[_lock lock];
|
|
OSCacheEntry *entry = _cache[key];
|
|
if (entry) {
|
|
_totalCost -= entry.cost;
|
|
entry.object = nil;
|
|
[_entryPool addObject:entry];
|
|
[_cache removeObjectForKey:key];
|
|
}
|
|
[_lock unlock];
|
|
}
|
|
|
|
- (void)removeAllObjects
|
|
{
|
|
NSAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
|
|
[_lock lock];
|
|
_totalCost = 0;
|
|
_sequenceNumber = 0;
|
|
for (OSCacheEntry *entry in _cache.allValues)
|
|
{
|
|
entry.object = nil;
|
|
[_entryPool addObject:entry];
|
|
}
|
|
[_cache removeAllObjects];
|
|
[_lock unlock];
|
|
}
|
|
|
|
- (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
|
|
{
|
|
if (block)
|
|
{
|
|
[_lock lock];
|
|
[_cache enumerateKeysAndObjectsUsingBlock:^(id key, OSCacheEntry *entry, BOOL *stop) {
|
|
block(key, entry.object, stop);
|
|
}];
|
|
[_lock unlock];
|
|
}
|
|
}
|
|
|
|
//handle unimplemented methods
|
|
|
|
- (BOOL)isKindOfClass:(Class)aClass
|
|
{
|
|
//pretend that we're an NSCache if anyone asks
|
|
if (aClass == [OSCache class] || aClass == [NSCache class])
|
|
{
|
|
return YES;
|
|
}
|
|
return [super isKindOfClass:aClass];
|
|
}
|
|
|
|
- (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 OSCache
|
|
|
|
+ (instancetype)allocWithZone:(struct _NSZone *)zone
|
|
{
|
|
return (OSCache *)[OSCache_Private allocWithZone:zone];
|
|
}
|
|
|
|
- (id)objectForKeyedSubscript:(__unused id<NSCopying>)key { return nil; }
|
|
- (void)setObject:(__unused id)obj forKeyedSubscript:(__unused id<NSCopying>)key {}
|
|
- (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; }
|
|
|
|
@end
|