From 6bbe31c18d108a1354f273f41368ca5d618faf55 Mon Sep 17 00:00:00 2001 From: Richard Frith-MacDonald Date: Tue, 1 Sep 2009 04:40:07 +0000 Subject: [PATCH] tweak NSCache changes git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@28582 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 15 +- Headers/Foundation/NSCache.h | 125 +++++++++---- Source/NSCache.m | 354 ++++++++++++++++++++--------------- 3 files changed, 309 insertions(+), 185 deletions(-) diff --git a/ChangeLog b/ChangeLog index d5c44e8f0..acd13502e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,18 @@ -2009-08-31 David Chisnall +2009-09-01 Richard Frith-Macdonald + * Headers/Foundation/NSCache.h: + * Source/NSCache.m: + Great additions... tweaked with whitespace changes to conform to + coding standards, addition of copyright headers, addition of standard + prevention of multiple inclusion, include changes to build independant + of any existing installation. + +2009-08-31 David Chisnall + + * Headers/Foundation/NSCache.h: + * Headers/Foundation/Foundation.h: + * Source/NSCache.m: + * Source/GNUmakefile: Added NSCache implementation. 2009-08-31 Richard Frith-Macdonald diff --git a/Headers/Foundation/NSCache.h b/Headers/Foundation/NSCache.h index 53a3690ff..d60207ceb 100644 --- a/Headers/Foundation/NSCache.h +++ b/Headers/Foundation/NSCache.h @@ -1,104 +1,152 @@ +/* Interface for NSCache for GNUStep + Copyright (C) 2009 Free Software Foundation, Inc. + + Written by: David Chisnall + Created: 2009 + + This file is part of the GNUstep Base Library. + + 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 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 + Library 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 02111 USA. + */ + +#ifndef __NSCache_h_GNUSTEP_BASE_INCLUDE +#define __NSCache_h_GNUSTEP_BASE_INCLUDE +#import + #if OS_API_VERSION(MAC_OS_X_VERSION_10_6, GS_API_LATEST) -#import +#import + +#if defined(__cplusplus) +extern "C" { +#endif @class NSString; @class NSMutableDictionary; @class NSMutableArray; + #ifndef __has_feature #define __has_feature(x) 0 #endif + @interface NSCache : NSObject { #if !__has_feature(objc_nonfragile_abi) || defined(EXPOSE_GSCACHE_IVARS) - @private - /** The maximum total cost of all cache objects. */ - NSUInteger _costLimit; - /** Total cost of currently-stored objects. */ - NSUInteger _totalCost; - /** The maximum number of objects in the cache. */ - NSUInteger _countLimit; - /** The delegate object, notified when objects are about to be evicted. */ - id _delegate; - /** Flag indicating whether discarded objects should be evicted */ - BOOL _evictsObjectsWithDiscardedContent; - /** Name of this cache. */ - NSString *_name; - /** The mapping from names to objects in this cache. */ - NSMutableDictionary *_objects; - /** LRU ordering of all potentially-evictable objects in this cache. */ - NSMutableArray *_accesses; - /** Total number of accesses to objects */ - int64_t _totalAccesses; + @private + /** The maximum total cost of all cache objects. */ + NSUInteger _costLimit; + /** Total cost of currently-stored objects. */ + NSUInteger _totalCost; + /** The maximum number of objects in the cache. */ + NSUInteger _countLimit; + /** The delegate object, notified when objects are about to be evicted. */ + id _delegate; + /** Flag indicating whether discarded objects should be evicted */ + BOOL _evictsObjectsWithDiscardedContent; + /** Name of this cache. */ + NSString *_name; + /** The mapping from names to objects in this cache. */ + NSMutableDictionary *_objects; + /** LRU ordering of all potentially-evictable objects in this cache. */ + NSMutableArray *_accesses; + /** Total number of accesses to objects */ + int64_t _totalAccesses; #endif } /** * Returns the maximum number of objects that are supported by this cache. */ -- (NSUInteger)countLimit; +- (NSUInteger) countLimit; + /** * Returns the cache's delegate. */ -- (id)delegate; +- (id) delegate; + /** * Returns whether objects stored in this cache which implement the * NSDiscardableContent protocol are removed from the cache when their contents * are evicted. */ -- (BOOL)evictsObjectsWithDiscardedContent; +- (BOOL) evictsObjectsWithDiscardedContent; + /** * Returns the name associated with this cache. */ -- (NSString*)name; +- (NSString*) name; + /** * Returns an object associated with the specified key in this cache. */ -- (id)objectForKey: (id)key; +- (id) objectForKey: (id)key; + /** * Removes all objects from this cache. */ -- (void)removeAllObjects; +- (void) removeAllObjects; + /** * Removes the object associated with the given key. */ -- (void)removeObjectForKey: (id)key; +- (void) removeObjectForKey: (id)key; + /** * Sets the maximum number of objects permitted in this cache. This limit is * advisory; caches may choose to disregard it temporarily or permanently. A * limit of 0 is used to indicate no limit; this is the default. */ -- (void)setCountLimit: (NSUInteger)lim;; +- (void) setCountLimit: (NSUInteger)lim;; + /** * Sets the delegate for this cache. The delegate will be notified when an * object is being evicted or removed from the cache. */ -- (void)setDelegate:(id)del; +- (void) setDelegate: (id)del; + /** * Sets whether this cache will evict objects that conform to the * NSDiscardableContent protocol, or simply discard their contents. */ -- (void)setEvictsObjectsWithDiscardedContent:(BOOL)b; +- (void) setEvictsObjectsWithDiscardedContent: (BOOL)b; + /** * Sets the name for this cache. */ -- (void)setName: (NSString*)cacheName; +- (void) setName: (NSString*)cacheName; + /** * Adds an object and its associated cost. The cache will endeavor to keep the * total cost below the value set with -setTotalCostLimit: by discarding the * contents of objects which implement the NSDiscardableContent protocol. */ -- (void)setObject: (id)obj forKey: (id)key cost: (NSUInteger)num; +- (void) setObject: (id)obj forKey: (id)key cost: (NSUInteger)num; + /** * Adds an object to the cache without associating a cost with it. */ -- (void)setObject: (id)obj forKey: (id)key; +- (void) setObject: (id)obj forKey: (id)key; + /** * Sets the maximum total cost for objects stored in this cache. This limit is * advisory; caches may choose to disregard it temporarily or permanently. A * limit of 0 is used to indicate no limit; this is the default. */ -- (void)setTotalCostLimit: (NSUInteger)lim; +- (void) setTotalCostLimit: (NSUInteger)lim; @end + /** * Protocol implemented by NSCache delegate objects. */ @@ -107,6 +155,13 @@ * Delegate method, called just before the cache removes an object, either as * the result of user action or due to the cache becoming full. */ -- (void)cache: (NSCache*)cache willEvictObject: (id)obj; +- (void) cache: (NSCache*)cache willEvictObject: (id)obj; @end + +#if defined(__cplusplus) +} +#endif + #endif //OS_API_VERSION(MAC_OS_X_VERSION_10_6, GS_API_LATEST) + +#endif /* __NSCache_h_GNUSTEP_BASE_INCLUDE */ diff --git a/Source/NSCache.m b/Source/NSCache.m index 3438d5ee4..3a8beb9de 100644 --- a/Source/NSCache.m +++ b/Source/NSCache.m @@ -1,5 +1,36 @@ +/* Implementation for NSCache for GNUStep + Copyright (C) 2009 Free Software Foundation, Inc. + + Written by: David Chisnall + Created: 2009 + + This file is part of the GNUstep Base Library. + + 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 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 + Library 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 02111 USA. + */ + +#import "config.h" + #define EXPOSE_GSCACHE_IVARS 1 -#import + +#import "Foundation/NSArray.h" +#import "Foundation/NSCache.h" +#import "Foundation/NSDictionary.h" +#import "Foundation/NSEnumerator.h" +#import "Foundation/NSString.h" /** * _GSCachedObject is effectively used as a structure containing the various @@ -9,134 +40,157 @@ */ @interface _GSCachedObject : NSObject { - @public - id object; - NSString *key; - int accessCount; - NSUInteger cost; - BOOL isEvictable; + @public + id object; + NSString *key; + int accessCount; + NSUInteger cost; + BOOL isEvictable; } @end @interface NSCache (EvictionPolicy) /** The method controlling eviction policy in an NSCache. */ -- (void)_evictObjectsToMakeSpaceForObjectWithCost: (NSUInteger)cost; +- (void) _evictObjectsToMakeSpaceForObjectWithCost: (NSUInteger)cost; @end @implementation NSCache -- (id)init +- (id) init { - if (nil == (self = [super init])) { return nil; } - _objects = [NSMutableDictionary new]; - _accesses = [NSMutableArray new]; - return self; + if (nil == (self = [super init])) + { + return nil; + } + _objects = [NSMutableDictionary new]; + _accesses = [NSMutableArray new]; + return self; } -- (NSUInteger)countLimit + +- (NSUInteger) countLimit { - return _countLimit; + return _countLimit; } -- (id)delegate + +- (id) delegate { - return _delegate; + return _delegate; } -- (BOOL)evictsObjectsWithDiscardedContent + +- (BOOL) evictsObjectsWithDiscardedContent { - return _evictsObjectsWithDiscardedContent; + return _evictsObjectsWithDiscardedContent; } -- (NSString*)name + +- (NSString*) name { - return _name; + return _name; } -- (id)objectForKey: (id)key + +- (id) objectForKey: (id)key { - _GSCachedObject *obj = [_objects objectForKey: key]; - if (nil == obj) - { - return nil; - } - if (obj->isEvictable) - { - // Move the object to the end of the access list. - [_accesses removeObjectIdenticalTo: obj]; - [_accesses addObject: obj]; - } - obj->accessCount++; - _totalAccesses++; - return obj->object; + _GSCachedObject *obj = [_objects objectForKey: key]; + + if (nil == obj) + { + return nil; + } + if (obj->isEvictable) + { + // Move the object to the end of the access list. + [_accesses removeObjectIdenticalTo: obj]; + [_accesses addObject: obj]; + } + obj->accessCount++; + _totalAccesses++; + return obj->object; } -- (void)removeAllObjects + +- (void) removeAllObjects { - NSEnumerator *e = [_objects objectEnumerator]; - _GSCachedObject *obj; - while (nil != (obj = [e nextObject])) - { - [_delegate cache: self willEvictObject: obj->object]; - } - [_objects removeAllObjects]; - [_accesses removeAllObjects]; - _totalAccesses = 0; + NSEnumerator *e = [_objects objectEnumerator]; + _GSCachedObject *obj; + + while (nil != (obj = [e nextObject])) + { + [_delegate cache: self willEvictObject: obj->object]; + } + [_objects removeAllObjects]; + [_accesses removeAllObjects]; + _totalAccesses = 0; } -- (void)removeObjectForKey: (id)key + +- (void) removeObjectForKey: (id)key { - _GSCachedObject *obj = [_objects objectForKey: key]; - if (nil != obj) - { - [_delegate cache: self willEvictObject: obj->object]; - _totalAccesses -= obj->accessCount; - [_objects removeObjectForKey: key]; - [_accesses removeObjectIdenticalTo: obj]; - } + _GSCachedObject *obj = [_objects objectForKey: key]; + + if (nil != obj) + { + [_delegate cache: self willEvictObject: obj->object]; + _totalAccesses -= obj->accessCount; + [_objects removeObjectForKey: key]; + [_accesses removeObjectIdenticalTo: obj]; + } } -- (void)setCountLimit: (NSUInteger)lim + +- (void) setCountLimit: (NSUInteger)lim { - _countLimit = lim; + _countLimit = lim; } -- (void)setDelegate:(id)del + +- (void) setDelegate:(id)del { - _delegate = del; + _delegate = del; } -- (void)setEvictsObjectsWithDiscardedContent:(BOOL)b + +- (void) setEvictsObjectsWithDiscardedContent:(BOOL)b { - _evictsObjectsWithDiscardedContent = b; + _evictsObjectsWithDiscardedContent = b; } -- (void)setName: (NSString*)cacheName + +- (void) setName: (NSString*)cacheName { - ASSIGN(_name, cacheName); + ASSIGN(_name, cacheName); } -- (void)setObject: (id)obj forKey: (id)key cost: (NSUInteger)num + +- (void) setObject: (id)obj forKey: (id)key cost: (NSUInteger)num { - _GSCachedObject *old = [_objects objectForKey: key]; - if (nil != old) - { - [self removeObjectForKey: old->key]; - } - [self _evictObjectsToMakeSpaceForObjectWithCost: num]; - _GSCachedObject *new = [_GSCachedObject new]; - // Retained here, released when obj is dealloc'd - new->object = RETAIN(obj); - new->key = RETAIN(key); - new->cost = num; - if ([obj conformsToProtocol: @protocol(NSDiscardableContent)]) - { - new->isEvictable = YES; - [_accesses addObject: new]; - } - [_objects setObject: new forKey: key]; - RELEASE(obj); - _totalCost += num; + _GSCachedObject *old = [_objects objectForKey: key]; + + if (nil != old) + { + [self removeObjectForKey: old->key]; + } + [self _evictObjectsToMakeSpaceForObjectWithCost: num]; + _GSCachedObject *new = [_GSCachedObject new]; + // Retained here, released when obj is dealloc'd + new->object = RETAIN(obj); + new->key = RETAIN(key); + new->cost = num; + if ([obj conformsToProtocol: @protocol(NSDiscardableContent)]) + { + new->isEvictable = YES; + [_accesses addObject: new]; + } + [_objects setObject: new forKey: key]; + RELEASE(obj); + _totalCost += num; } -- (void)setObject: (id)obj forKey: (id)key + +- (void) setObject: (id)obj forKey: (id)key { - [self setObject: obj forKey: key cost: 0]; + [self setObject: obj forKey: key cost: 0]; } -- (void)setTotalCostLimit: (NSUInteger)lim + +- (void) setTotalCostLimit: (NSUInteger)lim { - _costLimit = lim; + _costLimit = lim; } + - (NSUInteger)totalCostLimit { - return _costLimit; + return _costLimit; } + /** * This method is the one that handles the eviction policy. This * implementation uses a relatively simple LRU/LFU hybrid. The NSCache @@ -146,82 +200,84 @@ */ - (void)_evictObjectsToMakeSpaceForObjectWithCost: (NSUInteger)cost { - NSUInteger spaceNeeded = 0; - NSUInteger count = [_objects count]; + NSUInteger spaceNeeded = 0; + NSUInteger count = [_objects count]; - if (_costLimit > 0 && _totalCost + cost > _costLimit) + if (_costLimit > 0 && _totalCost + cost > _costLimit) + { + spaceNeeded = _totalCost + cost - _costLimit; + } + + // Only evict if we need the space. + if (count > 0 && (spaceNeeded > 0 || count >= _countLimit)) + { + NSMutableArray *evictedKeys = nil; + // Round up slightly. + NSUInteger averageAccesses = (_totalAccesses / count * 0.2) + 1; + NSEnumerator *e = [_accesses objectEnumerator]; + _GSCachedObject *obj; + + if (_evictsObjectsWithDiscardedContent) { - spaceNeeded = _totalCost + cost - _costLimit; + evictedKeys = [[NSMutableArray alloc] init]; } - - // Only evict if we need the space. - if (count > 0 && (spaceNeeded > 0 || count >= _countLimit)) + while (nil != (obj = [e nextObject])) { - NSMutableArray *evictedKeys = nil; - // Round up slightly. - NSUInteger averageAccesses = (_totalAccesses / count * 0.2) + 1; - NSEnumerator *e = [_accesses objectEnumerator]; - _GSCachedObject *obj; + // Don't evict frequently accessed objects. + if (obj->accessCount < averageAccesses && obj->isEvictable) + { + [obj->object discardContentIfPossible]; + if ([obj->object isContentDiscarded]) + { + NSUInteger cost = obj->cost; - if (_evictsObjectsWithDiscardedContent) - { - evictedKeys = [[NSMutableArray alloc] init]; - } - while (nil != (obj = [e nextObject])) - { - // Don't evict frequently accessed objects. - if (obj->accessCount < averageAccesses && obj->isEvictable) - { - [obj->object discardContentIfPossible]; - if ([obj->object isContentDiscarded]) - { - NSUInteger cost = obj->cost; - // Evicted objects have no cost. - obj->cost = 0; - // Don't try evicting this again in future; it's gone already. - obj->isEvictable = NO; - // Remove this object as well as its contents if required - if (_evictsObjectsWithDiscardedContent) - { - [evictedKeys addObject: obj->key]; - } - _totalCost -= cost; - // If we've freed enough space, give up - if (cost > spaceNeeded) - { - break; - } - spaceNeeded -= cost; - } - } - } - // Evict all of the objects whose content we have discarded if required - if (_evictsObjectsWithDiscardedContent) - { - NSString *key; - e = [evictedKeys objectEnumerator]; - while (nil != (key = [e nextObject])) - { - [self removeObjectForKey: key]; - } + // Evicted objects have no cost. + obj->cost = 0; + // Don't try evicting this again in future; it's gone already. + obj->isEvictable = NO; + // Remove this object as well as its contents if required + if (_evictsObjectsWithDiscardedContent) + { + [evictedKeys addObject: obj->key]; + } + _totalCost -= cost; + // If we've freed enough space, give up + if (cost > spaceNeeded) + { + break; + } + spaceNeeded -= cost; } + } } + // Evict all of the objects whose content we have discarded if required + if (_evictsObjectsWithDiscardedContent) + { + NSString *key; + + e = [evictedKeys objectEnumerator]; + while (nil != (key = [e nextObject])) + { + [self removeObjectForKey: key]; + } + } + } } -- (void)dealloc +- (void) dealloc { - [_name release]; - [_objects release]; - [_accesses release]; - [super dealloc]; + [_name release]; + [_objects release]; + [_accesses release]; + [super dealloc]; } @end @implementation _GSCachedObject - (void)dealloc { - [object release]; - [key release]; - [super dealloc]; + [object release]; + [key release]; + [super dealloc]; } @end