mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-10-09 02:11:00 +00:00
Merge pull request #2321 from william57m/master
New: added the CPCache class
This commit is contained in:
commit
630aa53d98
4 files changed
+797
No files matched your search
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* CPCache.j
|
||||
* Foundation
|
||||
*
|
||||
* Created by William Mura.
|
||||
* Copyright 2015, William Mura.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
|
||||
@import "CPObject.j"
|
||||
@import "CPDictionary.j"
|
||||
@import "CPString.j"
|
||||
|
||||
@class _CPCacheItem;
|
||||
|
||||
|
||||
/*
|
||||
* Delegate CPCacheDelegate
|
||||
*
|
||||
* - cache:willEvictObject: is called when a object is going to be removed
|
||||
* When the total cost or the count exceeds the total cost limit or the count limit
|
||||
* And also when removeObjectForKey or removeAllObjects are called
|
||||
*/
|
||||
@protocol CPCacheDelegate <CPObject>
|
||||
|
||||
@optional
|
||||
- (void)cache:(CPCache)cache willEvictObject:(id)obj;
|
||||
|
||||
@end
|
||||
|
||||
var CPCacheDelegate_cache_willEvictObject_ = 1 << 1;
|
||||
|
||||
|
||||
/*!
|
||||
@class CPCache
|
||||
@ingroup foundation
|
||||
@brief A collection-like container with discardable objects
|
||||
|
||||
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSCache_Class/index.html#//apple_ref/occ/instp/NSCache/delegate
|
||||
|
||||
A CPCache object is a collection-like container, or cache, that stores key-value pairs,
|
||||
similar to the CPDictionary class. Developers often incorporate caches to temporarily
|
||||
store objects with transient data that are expensive to create.
|
||||
|
||||
Reusing these objects can provide performance benefits, because their values do not have to be recalculated.
|
||||
However, the objects are not critical to the application and can be discarded if memory is tight.
|
||||
If discarded, their values will have to be recomputed again when needed.
|
||||
*/
|
||||
@implementation CPCache : CPObject
|
||||
{
|
||||
CPDictionary _items;
|
||||
int _currentPosition;
|
||||
BOOL _totalCostCache;
|
||||
unsigned _implementedDelegateMethods;
|
||||
|
||||
CPString _name @accessors(property=name);
|
||||
int _countLimit @accessors(property=countLimit);
|
||||
int _totalCostLimit @accessors(property=totalCostLimit);
|
||||
id <CPCacheDelegate> _delegate @accessors(property=delegate);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Initialization
|
||||
|
||||
/*!
|
||||
Initializes the cache with default values
|
||||
@return the initialized cache
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_items = [[CPDictionary alloc] init];
|
||||
_currentPosition = 0;
|
||||
_totalCostCache = -1;
|
||||
_implementedDelegateMethods = 0;
|
||||
|
||||
_name = @"";
|
||||
_countLimit = 0;
|
||||
_totalCostLimit = 0;
|
||||
_delegate = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Managing cache
|
||||
|
||||
/*!
|
||||
Returns the object which correspond to the given key
|
||||
@param aKey the key for the object's entry
|
||||
@return the object for the entry
|
||||
*/
|
||||
- (id)objectForKey:(id)aKey
|
||||
{
|
||||
return [[_items objectForKey:aKey] object];
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds an object with default cost into the cache.
|
||||
@param anObject the object to add in the cache
|
||||
@param aKey the object's key
|
||||
*/
|
||||
- (void)setObject:(id)anObject forKey:(id)aKey
|
||||
{
|
||||
[self setObject:anObject forKey:aKey cost:0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds an object with a cost into the cache.
|
||||
@param anObject the object to add in the cache
|
||||
@param aKey the object's key
|
||||
@param aCost the object's cost
|
||||
*/
|
||||
- (void)setObject:(id)anObject forKey:(id)aKey cost:(int)aCost
|
||||
{
|
||||
// Check if the key already exist
|
||||
if ([_items objectForKey:aKey])
|
||||
[self removeObjectForKey:aKey];
|
||||
|
||||
// Add object
|
||||
[_items setObject:[_CPCacheItem cacheItemWithObject:anObject cost:aCost position:++_currentPosition] forKey:aKey];
|
||||
|
||||
// Invalid cost cache
|
||||
_totalCostCache = -1;
|
||||
|
||||
// Clean cache to satisfy condition (< totalCostLimit & < countLimit) if necessary
|
||||
[self _cleanCache];
|
||||
}
|
||||
|
||||
/*!
|
||||
Removes the object from the cache for the given key.
|
||||
@param aKey the key of the object to be removed
|
||||
*/
|
||||
- (void)removeObjectForKey:(id)aKey
|
||||
{
|
||||
// Call delegate method to warn that the object is going to be removed
|
||||
[self _sendDelegateWillEvictObjectForKey:aKey];
|
||||
|
||||
// Remove object
|
||||
[_items removeObjectForKey:aKey];
|
||||
|
||||
// Invalid cost cache
|
||||
_totalCostCache = -1;
|
||||
}
|
||||
|
||||
/*!
|
||||
Removes all the objects from the cache.
|
||||
*/
|
||||
- (void)removeAllObjects
|
||||
{
|
||||
// Call delegate method to warn that the objects are going to be removed
|
||||
var enumerator = [_items keyEnumerator],
|
||||
key;
|
||||
|
||||
while (key = [enumerator nextObject])
|
||||
[self _sendDelegateWillEvictObjectForKey:key]
|
||||
|
||||
// Remove all objects
|
||||
[_items removeAllObjects];
|
||||
|
||||
// Invalid cost cache and reset position counter
|
||||
_totalCostCache = -1;
|
||||
_currentPosition = 0;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Setters
|
||||
|
||||
/*!
|
||||
Sets the count limit of the cache.
|
||||
Remove objects if not enough place to keep all of them
|
||||
@param aCountLimit the new count limit
|
||||
*/
|
||||
- (void)setCountLimit:(int)aCountLimit
|
||||
{
|
||||
_countLimit = aCountLimit;
|
||||
[self _cleanCache];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the total cost limit of the cache.
|
||||
Remove objects if not enough place to keep all of them
|
||||
@param aTotalCostLimit the new total cost limit
|
||||
*/
|
||||
- (void)setTotalCostLimit:(int)aTotalCostLimit
|
||||
{
|
||||
_totalCostLimit = aTotalCostLimit;
|
||||
[self _cleanCache];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the cache's delegate.
|
||||
@param aDelegate the new delegate
|
||||
*/
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
if (_delegate === aDelegate)
|
||||
return;
|
||||
|
||||
_delegate = aDelegate;
|
||||
_implementedDelegateMethods = 0;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(cache:willEvictObject:)])
|
||||
_implementedDelegateMethods |= CPCacheDelegate_cache_willEvictObject_
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Privates
|
||||
|
||||
/*
|
||||
* This method return the number of objects in the cache
|
||||
*/
|
||||
- (int)_count
|
||||
{
|
||||
return [_items count];
|
||||
}
|
||||
|
||||
/*
|
||||
* This method return the total cost (addition of all object's cost in the cache)
|
||||
*/
|
||||
- (int)_totalCost
|
||||
{
|
||||
if (_totalCostCache >= 0)
|
||||
return _totalCostCache;
|
||||
|
||||
var enumerator = [_items objectEnumerator],
|
||||
value;
|
||||
|
||||
_totalCostCache = 0;
|
||||
|
||||
while (value = [enumerator nextObject])
|
||||
_totalCostCache += [value cost];
|
||||
|
||||
return _totalCostCache;
|
||||
}
|
||||
|
||||
/*
|
||||
* This method resequence the position of objects
|
||||
* Otherwise the position value could rise until to cause problem
|
||||
*/
|
||||
- (void)_resequencePosition
|
||||
{
|
||||
_currentPosition = 1;
|
||||
|
||||
// Sort keys by position
|
||||
var sortedKeys = [[_items allKeys] sortedArrayUsingFunction:
|
||||
function(k1, k2) {
|
||||
return [[[_items objectForKey:k1] position] compare:[[_items objectForKey:k2] position]]; }];
|
||||
|
||||
// Affect new positions
|
||||
for (var i = 0; i < sortedKeys.length; ++i)
|
||||
[[_items objectForKey:sortedKeys[i]] setPosition:_currentPosition++];
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the totalCostLimit is exceeded
|
||||
*/
|
||||
- (BOOL)_isTotalCostLimitExceeded
|
||||
{
|
||||
return ([self _totalCost] > _totalCostLimit && _totalCostLimit > 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the countLimit is exceeded
|
||||
*/
|
||||
- (BOOL)_isCountLimitExceeded
|
||||
{
|
||||
return ([self _count] > _countLimit && _countLimit > 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* This method clean the cache if the totalCost or the count exceeds the totalCostLimit or the countLimit
|
||||
* until to satisfy condition (totalCost < totalCostLimit and count < countLimit)
|
||||
*/
|
||||
- (void)_cleanCache
|
||||
{
|
||||
// Check if the condition is satisfied
|
||||
if (![self _isTotalCostLimitExceeded] && ![self _isCountLimitExceeded])
|
||||
return;
|
||||
|
||||
// Sort keys by position
|
||||
var sortedKeys = [[_items allKeys] sortedArrayUsingFunction:
|
||||
function(k1, k2) {
|
||||
return [[[_items objectForKey:k1] position] compare:[[_items objectForKey:k2] position]]; }];
|
||||
|
||||
// Remove oldest objects until to satisfy the break condition
|
||||
for (var i = 0; i < sortedKeys.length; ++i)
|
||||
{
|
||||
if (![self _isTotalCostLimitExceeded] && ![self _isCountLimitExceeded])
|
||||
break;
|
||||
|
||||
// Call delegate method to warn that the object is going to be removed
|
||||
[self _sendDelegateWillEvictObjectForKey:sortedKeys[i]];
|
||||
|
||||
// Remove object
|
||||
[_items removeObjectForKey: sortedKeys[i]];
|
||||
|
||||
// Invalid cost cache
|
||||
_totalCostCache = -1;
|
||||
}
|
||||
|
||||
// Resequence position of all objects
|
||||
[self _resequencePosition];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CPCache (CPCacheDelegate)
|
||||
|
||||
- (void)_sendDelegateWillEvictObjectForKey:(id)aKey
|
||||
{
|
||||
if (_implementedDelegateMethods & CPCacheDelegate_cache_willEvictObject_)
|
||||
[_delegate cache:self willEvictObject:[[_items objectForKey:aKey] object]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/*
|
||||
* Class _CPCacheItem
|
||||
* Represent an item of CPCache
|
||||
* This class allow to associate a cost and a position to an object
|
||||
*
|
||||
* Attributes:
|
||||
* - object: the stored object
|
||||
* - cost: represent the cost (of memory) of the object
|
||||
* - position: represent the insertion order to determine the oldest object
|
||||
*/
|
||||
@implementation _CPCacheItem : CPObject
|
||||
{
|
||||
CPObject _object @accessors(property=object);
|
||||
int _cost @accessors(property=cost);
|
||||
int _position @accessors(property=position);
|
||||
}
|
||||
|
||||
+ (id)cacheItemWithObject:(CPObject)anObject cost:(int)aCost position:(int)aPosition
|
||||
{
|
||||
var cacheItem = [[self alloc] init];
|
||||
|
||||
if (cacheItem)
|
||||
{
|
||||
[cacheItem setObject:anObject];
|
||||
[cacheItem setCost:aCost];
|
||||
[cacheItem setPosition:aPosition];
|
||||
}
|
||||
|
||||
return cacheItem;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -24,6 +24,7 @@
|
||||
@import "CPArray.j"
|
||||
@import "CPBundle.j"
|
||||
@import "CPByteCountFormatter.j"
|
||||
@import "CPCache.j"
|
||||
@import "CPCharacterSet.j"
|
||||
@import "CPCoder.j"
|
||||
@import "CPComparisonPredicate.j"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
@import <Foundation/CPCache.j>
|
||||
|
||||
|
||||
@implementation CPCacheItemTest : OJTestCase
|
||||
|
||||
- (void)testCacheItemWithObjectCostPosition
|
||||
{
|
||||
var cacheItem = [_CPCacheItem cacheItemWithObject:"Hello Cappuccino!" cost:50 position:1];
|
||||
[self assert:[cacheItem object] equals:"Hello Cappuccino!"];
|
||||
[self assert:[cacheItem cost] equals:50];
|
||||
[self assert:[cacheItem position] equals:1];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,411 @@
|
||||
@import <Foundation/CPCache.j>
|
||||
|
||||
|
||||
/*
|
||||
* CPCacheTest tests all methods from CPCache
|
||||
*/
|
||||
@implementation CPCacheTest : OJTestCase<CPCacheDelegate>
|
||||
{
|
||||
// Parameters to check delegate
|
||||
int _countDelegateExecuted;
|
||||
CPArray _caches;
|
||||
CPArray _objects;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
// Initialize data to check delegate
|
||||
_countDelegateExecuted = 0;
|
||||
_caches = [[CPArray alloc] init];
|
||||
_objects = [[CPArray alloc] init];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Delegate method, called when an object is evicted
|
||||
*/
|
||||
- (void)cache:(CPCache)cache willEvictObject:(id)obj
|
||||
{
|
||||
_countDelegateExecuted++;
|
||||
[_caches addObject:cache];
|
||||
[_objects addObject:obj];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Tests methods of CPCache
|
||||
*/
|
||||
- (void)testInit
|
||||
{
|
||||
var cache = [[CPCache alloc] init];
|
||||
|
||||
// Private attributes
|
||||
[self assert:CPCache equals:[cache class]];
|
||||
[self assert:CPDictionary equals:[cache._items class]];
|
||||
[self assert:0 equals:cache._currentPosition];
|
||||
[self assert:-1 equals:cache._totalCostCache];
|
||||
[self assert:0 equals:cache._implementedDelegateMethods];
|
||||
|
||||
// Public attributes
|
||||
[self assert:@"" equals:[cache name]];
|
||||
[self assert:0 equals:[cache countLimit]];
|
||||
[self assert:0 equals:[cache totalCostLimit]];
|
||||
[self assert:nil equals:[cache delegate]];
|
||||
}
|
||||
|
||||
- (void)testObjectForKey
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setObject:@"Object1" forKey:@"key1"];
|
||||
|
||||
// With valid key
|
||||
[self assert:@"Object1" equals:[cache objectForKey:@"key1"]];
|
||||
|
||||
// With invalid key
|
||||
[self assert:nil equals:[cache objectForKey:@"key666"]];
|
||||
|
||||
// With nil key
|
||||
[self assert:nil equals:[cache objectForKey:nil]];
|
||||
}
|
||||
|
||||
- (void)testSetObjectForKeyDefault
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setDelegate:self];
|
||||
|
||||
// Check result when add key in empty cache
|
||||
[cache setObject:@"Object1" forKey:@"key1"]
|
||||
[self assert:@"Object1" equals:[cache objectForKey:@"key1"]];
|
||||
|
||||
// Check delegate
|
||||
[self assert:0 equals:_countDelegateExecuted];
|
||||
[self assert:0 equals:[_caches count]];
|
||||
[self assert:0 equals:[_objects count]];
|
||||
}
|
||||
|
||||
- (void)testSetObjectForKeyWithExistingKey
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setDelegate:self];
|
||||
[cache setObject:@"Object1" forKey:@"key1"];
|
||||
[cache setObject:@"Object2" forKey:@"key2"];
|
||||
|
||||
// Check result when add object with existing key in cache
|
||||
[cache setObject:@"Object3" forKey:@"key1"];
|
||||
[self assert:@"Object3" equals:[cache objectForKey:@"key1"]];
|
||||
[self assert:@"Object2" equals:[cache objectForKey:@"key2"]];
|
||||
|
||||
// Check delegate
|
||||
[self assert:1 equals:_countDelegateExecuted];
|
||||
[self assert:cache equals:[_caches objectAtIndex:0]];
|
||||
[self assert:@"Object1" equals:[_objects objectAtIndex:0]];
|
||||
}
|
||||
|
||||
- (void)testSetObjectForKeyDiscardByCost
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setDelegate:self];
|
||||
[cache setTotalCostLimit:100];
|
||||
[cache setObject:@"Object1" forKey:@"key1" cost:50];
|
||||
[cache setObject:@"Object2" forKey:@"key2" cost:50];
|
||||
|
||||
// Check result when add key in cache with total cost limit
|
||||
[cache setObject:@"Object3" forKey:@"key3" cost:1];
|
||||
[self assert:nil equals:[cache objectForKey:@"key1"]];
|
||||
[self assert:@"Object2" equals:[cache objectForKey:@"key2"]];
|
||||
[self assert:@"Object3" equals:[cache objectForKey:@"key3"]];
|
||||
|
||||
// Check delegate
|
||||
[self assert:1 equals:_countDelegateExecuted];
|
||||
[self assert:cache equals:[_caches objectAtIndex:0]];
|
||||
[self assert:@"Object1" equals:[_objects objectAtIndex:0]];
|
||||
}
|
||||
|
||||
- (void)testSetObjectForKeyDiscardByCount
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setDelegate:self];
|
||||
[cache setCountLimit:2];
|
||||
[cache setObject:@"Object1" forKey:@"key1"];
|
||||
[cache setObject:@"Object2" forKey:@"key2"];
|
||||
|
||||
// Check result when add key in cache with count limit
|
||||
[cache setObject:@"Object3" forKey:@"key3"];
|
||||
[self assert:nil equals:[cache objectForKey:@"key1"]];
|
||||
[self assert:@"Object2" equals:[cache objectForKey:@"key2"]];
|
||||
[self assert:@"Object3" equals:[cache objectForKey:@"key3"]];
|
||||
|
||||
// Check delegate
|
||||
[self assert:1 equals:_countDelegateExecuted];
|
||||
[self assert:cache equals:[_caches objectAtIndex:0]];
|
||||
[self assert:@"Object1" equals:[_objects objectAtIndex:0]];
|
||||
}
|
||||
|
||||
- (void)testRemoveObjectForKey
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setDelegate:self];
|
||||
[cache setObject:@"Object1" forKey:@"key1"];
|
||||
|
||||
// Remove object
|
||||
[cache removeObjectForKey:@"key1"];
|
||||
[self assert:nil equals:[cache objectForKey:@"key1"]];
|
||||
|
||||
// Check delegate
|
||||
[self assert:1 equals:_countDelegateExecuted];
|
||||
[self assert:cache equals:[_caches objectAtIndex:0]];
|
||||
[self assert:@"Object1" equals:[_objects objectAtIndex:0]];
|
||||
}
|
||||
|
||||
- (void)testRemoveAllObjects
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setDelegate:self];
|
||||
[cache setObject:@"Object1" forKey:@"key1"];
|
||||
[cache setObject:@"Object2" forKey:@"key2"];
|
||||
|
||||
// Remove all objects
|
||||
[cache removeAllObjects];
|
||||
[self assert:nil equals:[cache objectForKey:@"key1"]];
|
||||
[self assert:nil equals:[cache objectForKey:@"key2"]];
|
||||
|
||||
// Check delegate
|
||||
[self assert:2 equals:_countDelegateExecuted];
|
||||
[self assert:cache equals:[_caches objectAtIndex:0]];
|
||||
[self assert:@"Object1" equals:[_objects objectAtIndex:0]];
|
||||
[self assert:cache equals:[_caches objectAtIndex:1]];
|
||||
[self assert:@"Object2" equals:[_objects objectAtIndex:1]];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Accessors and mutators
|
||||
*/
|
||||
|
||||
- (void)testSetCountLimit
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setDelegate:self];
|
||||
[cache setObject:@"Object1" forKey:@"key1"];
|
||||
[cache setObject:@"Object2" forKey:@"key2"];
|
||||
[cache setObject:@"Object3" forKey:@"key3"];
|
||||
|
||||
// Set count limit
|
||||
[cache setCountLimit:2];
|
||||
[self assert:nil equals:[cache objectForKey:@"key1"]];
|
||||
[self assert:@"Object2" equals:[cache objectForKey:@"key2"]];
|
||||
[self assert:@"Object3" equals:[cache objectForKey:@"key3"]];
|
||||
|
||||
// Check delegate
|
||||
[self assert:1 equals:_countDelegateExecuted];
|
||||
[self assert:cache equals:[_caches objectAtIndex:0]];
|
||||
[self assert:@"Object1" equals:[_objects objectAtIndex:0]];
|
||||
|
||||
// Check new count limit
|
||||
[self assert:2 equals:[cache countLimit]];
|
||||
}
|
||||
|
||||
- (void)testSetTotalCostLimit
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setDelegate:self];
|
||||
[cache setObject:@"Object1" forKey:@"key1" cost:50];
|
||||
[cache setObject:@"Object2" forKey:@"key2" cost:10];
|
||||
[cache setObject:@"Object3" forKey:@"key3" cost:70];
|
||||
|
||||
// Set total cost limit
|
||||
[cache setTotalCostLimit:100];
|
||||
[self assert:nil equals:[cache objectForKey:@"key1"]];
|
||||
[self assert:@"Object2" equals:[cache objectForKey:@"key2"]];
|
||||
[self assert:@"Object3" equals:[cache objectForKey:@"key3"]];
|
||||
|
||||
// Check delegate
|
||||
[self assert:1 equals:_countDelegateExecuted];
|
||||
[self assert:cache equals:[_caches objectAtIndex:0]];
|
||||
[self assert:@"Object1" equals:[_objects objectAtIndex:0]];
|
||||
|
||||
// Check total cost limit
|
||||
[self assert:100 equals:[cache totalCostLimit]];
|
||||
}
|
||||
|
||||
- (void)testSetDelegate
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setDelegate:self];
|
||||
|
||||
[self assert:self equals:[cache delegate]];
|
||||
[self assert:(1 << 1) equals:cache._implementedDelegateMethods]
|
||||
}
|
||||
|
||||
/*
|
||||
* Test private methods
|
||||
*/
|
||||
|
||||
- (void)testCount
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setObject:@"Object1" forKey:@"key1" cost:50];
|
||||
[cache setObject:@"Object2" forKey:@"key2" cost:10];
|
||||
[cache setObject:@"Object3" forKey:@"key3" cost:70];
|
||||
|
||||
[self assert:3 equals:[cache _count]];
|
||||
}
|
||||
|
||||
- (void)testTotalCost
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setObject:@"Object1" forKey:@"key1" cost:50];
|
||||
[cache setObject:@"Object2" forKey:@"key2" cost:10];
|
||||
[cache setObject:@"Object3" forKey:@"key3" cost:70];
|
||||
|
||||
[self assert:130 equals:[cache _totalCost]];
|
||||
|
||||
// Hack totalCost then invalidate it to be sure that it is recomputed
|
||||
cache._totalCostCache = 650;
|
||||
[self assert:650 equals:[cache _totalCost]];
|
||||
|
||||
cache._totalCostCache = -1;
|
||||
[self assert:130 equals:[cache _totalCost]];
|
||||
}
|
||||
|
||||
- (void)testResequencePosition
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setObject:@"Object1" forKey:@"key1" cost:50]; // 1 - 1
|
||||
[cache setObject:@"Object2" forKey:@"key2" cost:10];
|
||||
[cache setObject:@"Object3" forKey:@"key3" cost:70]; // 3 - 2
|
||||
[cache removeObjectForKey:@"key2"]
|
||||
[cache setObject:@"Object4" forKey:@"key4" cost:70]; // 4 - 3
|
||||
|
||||
// Before resequence
|
||||
[self assert:1 equals:[[cache._items objectForKey:@"key1"] position]];
|
||||
[self assert:3 equals:[[cache._items objectForKey:@"key3"] position]];
|
||||
[self assert:4 equals:[[cache._items objectForKey:@"key4"] position]];
|
||||
|
||||
[cache _resequencePosition];
|
||||
|
||||
// After resequence
|
||||
[self assert:1 equals:[[cache._items objectForKey:@"key1"] position]];
|
||||
[self assert:2 equals:[[cache._items objectForKey:@"key3"] position]];
|
||||
[self assert:3 equals:[[cache._items objectForKey:@"key4"] position]];
|
||||
}
|
||||
|
||||
- (void)testIsTotalCostLimitExceeded
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setObject:@"Object1" forKey:@"key1" cost:50];
|
||||
|
||||
// Not exceed (because of not limit)
|
||||
[self assert:NO equals:[cache _isTotalCostLimitExceeded]];
|
||||
|
||||
// Not exceed (with limit)
|
||||
[cache setTotalCostLimit:100];
|
||||
[self assert:NO equals:[cache _isTotalCostLimitExceeded]];
|
||||
|
||||
// Exceed (by hacking the cost of object)
|
||||
[[cache._items objectForKey:@"key1"] setCost: 150];
|
||||
cache._totalCostCache = -1;
|
||||
[self assert:YES equals:[cache _isTotalCostLimitExceeded]];
|
||||
}
|
||||
|
||||
- (void)testIsCountLimitExceeded
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setObject:@"Object1" forKey:@"key1"];
|
||||
|
||||
// Not exceed (because of not limit)
|
||||
[self assert:NO equals:[cache _isCountLimitExceeded]];
|
||||
|
||||
// Not exceed (with limit)
|
||||
[cache setCountLimit:1];
|
||||
[self assert:NO equals:[cache _isCountLimitExceeded]];
|
||||
|
||||
// Exceed (by hacking the items)
|
||||
[cache._items setObject:[_CPCacheItem cacheItemWithObject:@"Object2" cost:0 position:3] forKey:@"key2"];
|
||||
[self assert:YES equals:[cache _isCountLimitExceeded]];
|
||||
}
|
||||
|
||||
- (void)testCleanCacheWithoutDiscarding
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setTotalCostLimit: 100];
|
||||
[cache setCountLimit: 2];
|
||||
[cache setDelegate:self];
|
||||
[cache setObject:@"Object1" forKey:@"key1" cost:50];
|
||||
[cache setObject:@"Object2" forKey:@"key2" cost:40];
|
||||
|
||||
// Set total cost limit
|
||||
[cache _cleanCache];
|
||||
[self assert:@"Object1" equals:[cache objectForKey:@"key1"]];
|
||||
[self assert:@"Object2" equals:[cache objectForKey:@"key2"]];
|
||||
|
||||
// Check delegate
|
||||
[self assert:0 equals:_countDelegateExecuted];
|
||||
}
|
||||
|
||||
- (void)testCleanCacheWithCostDiscarding
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setTotalCostLimit: 100];
|
||||
[cache setDelegate:self];
|
||||
[cache setObject:@"Object1" forKey:@"key1" cost:50];
|
||||
[cache setObject:@"Object2" forKey:@"key2" cost:40];
|
||||
|
||||
// Hack cost of object
|
||||
[[cache._items objectForKey:@"key2"] setCost:80];
|
||||
cache._totalCostCache = -1;
|
||||
|
||||
// Set total cost limit
|
||||
[cache _cleanCache];
|
||||
[self assert:nil equals:[cache objectForKey:@"key1"]];
|
||||
[self assert:@"Object2" equals:[cache objectForKey:@"key2"]];
|
||||
|
||||
// Check delegate
|
||||
[self assert:1 equals:_countDelegateExecuted];
|
||||
[self assert:cache equals:[_caches objectAtIndex:0]];
|
||||
[self assert:@"Object1" equals:[_objects objectAtIndex:0]];
|
||||
}
|
||||
|
||||
- (void)testCleanCacheWithCountDiscarding
|
||||
{
|
||||
// Setup cache
|
||||
var cache = [[CPCache alloc] init];
|
||||
[cache setCountLimit:2];
|
||||
[cache setDelegate:self];
|
||||
[cache setObject:@"Object1" forKey:@"key1"];
|
||||
[cache setObject:@"Object2" forKey:@"key2"];
|
||||
|
||||
// Hack items
|
||||
[cache._items setObject:[_CPCacheItem cacheItemWithObject:@"Object3" cost:0 position:3] forKey:@"key3"];
|
||||
|
||||
// Set total cost limit
|
||||
[cache _cleanCache];
|
||||
[self assert:nil equals:[cache objectForKey:@"key1"]];
|
||||
[self assert:@"Object2" equals:[cache objectForKey:@"key2"]];
|
||||
[self assert:@"Object3" equals:[cache objectForKey:@"key3"]];
|
||||
|
||||
// Check delegate
|
||||
[self assert:1 equals:_countDelegateExecuted];
|
||||
[self assert:cache equals:[_caches objectAtIndex:0]];
|
||||
[self assert:@"Object1" equals:[_objects objectAtIndex:0]];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user