mirror of
https://github.com/gnustep/libs-performance.git
synced 2025-02-19 10:01:20 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/performance/trunk@36999 72102866-910b-0410-8b05-ffd578937521
957 lines
20 KiB
Objective-C
957 lines
20 KiB
Objective-C
/* -*-objc-*- */
|
|
|
|
/** Implementation of GSCache for GNUStep
|
|
Copyright (C) 2005 Free Software Foundation, Inc.
|
|
|
|
Written by: Richard Frith-Macdonald <rfm@gnu.org>
|
|
Date: October 2005
|
|
|
|
This file is part of the Performance 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 3 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., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
|
|
|
|
$Date$ $Revision$
|
|
*/
|
|
|
|
#import <Foundation/NSArray.h>
|
|
#import <Foundation/NSAutoreleasePool.h>
|
|
#import <Foundation/NSData.h>
|
|
#import <Foundation/NSDate.h>
|
|
#import <Foundation/NSDebug.h>
|
|
#import <Foundation/NSDictionary.h>
|
|
#import <Foundation/NSEnumerator.h>
|
|
#import <Foundation/NSException.h>
|
|
#import <Foundation/NSHashTable.h>
|
|
#import <Foundation/NSLock.h>
|
|
#import <Foundation/NSMapTable.h>
|
|
#import <Foundation/NSNotification.h>
|
|
#import <Foundation/NSSet.h>
|
|
#import <Foundation/NSString.h>
|
|
#import <Foundation/NSThread.h>
|
|
#import <Foundation/NSValue.h>
|
|
|
|
#import "GSCache.h"
|
|
#import "GSTicker.h"
|
|
|
|
#if !defined(GNUSTEP)
|
|
#include <objc/objc-class.h>
|
|
#if (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4)
|
|
#define class_getInstanceSize(isa) ((struct objc_class *)isa)->instance_size
|
|
#endif
|
|
#endif
|
|
|
|
@interface GSCache (Threading)
|
|
+ (void) _becomeThreaded: (NSNotification*)n;
|
|
- (void) _createLock;
|
|
@end
|
|
|
|
@interface GSCacheItem : NSObject
|
|
{
|
|
@public
|
|
GSCacheItem *next;
|
|
GSCacheItem *prev;
|
|
unsigned life;
|
|
unsigned warn;
|
|
unsigned when;
|
|
NSUInteger size;
|
|
id key;
|
|
id object;
|
|
}
|
|
+ (GSCacheItem*) newWithObject: (id)anObject forKey: (id)aKey;
|
|
@end
|
|
|
|
@implementation GSCacheItem
|
|
+ (GSCacheItem*) newWithObject: (id)anObject forKey: (id)aKey
|
|
{
|
|
GSCacheItem *i;
|
|
|
|
i = (GSCacheItem*)NSAllocateObject(self, 0, NSDefaultMallocZone());
|
|
i->object = [anObject retain];
|
|
i->key = [aKey copy];
|
|
return i;
|
|
}
|
|
- (void) dealloc
|
|
{
|
|
[key release];
|
|
[object release];
|
|
[super dealloc];
|
|
}
|
|
@end
|
|
|
|
|
|
@implementation GSCache
|
|
|
|
static NSHashTable *allCaches = 0;
|
|
static NSRecursiveLock *allCachesLock = nil;
|
|
static int itemOffset = 0;
|
|
|
|
typedef struct {
|
|
id delegate;
|
|
void (*refresh)(id, SEL, id, id, unsigned, unsigned);
|
|
BOOL (*replace)(id, SEL, id, id, unsigned, unsigned);
|
|
unsigned currentObjects;
|
|
NSUInteger currentSize;
|
|
unsigned lifetime;
|
|
unsigned maxObjects;
|
|
NSUInteger maxSize;
|
|
unsigned hits;
|
|
unsigned misses;
|
|
NSMapTable *contents;
|
|
GSCacheItem *first;
|
|
NSString *name;
|
|
NSMutableSet *exclude;
|
|
NSRecursiveLock *lock;
|
|
} Item;
|
|
#define my ((Item*)((void*)self + itemOffset))
|
|
|
|
/*
|
|
* Add item to linked list starting at *first
|
|
*/
|
|
static void appendItem(GSCacheItem *item, GSCacheItem **first)
|
|
{
|
|
if (*first == nil)
|
|
{
|
|
item->next = item->prev = item;
|
|
*first = item;
|
|
}
|
|
else
|
|
{
|
|
(*first)->prev->next = item;
|
|
item->prev = (*first)->prev;
|
|
(*first)->prev = item;
|
|
item->next = *first;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Remove item from linked list starting at *first
|
|
*/
|
|
static void removeItem(GSCacheItem *item, GSCacheItem **first)
|
|
{
|
|
if (*first == item)
|
|
{
|
|
if (item->next == item)
|
|
{
|
|
*first = nil;
|
|
}
|
|
else
|
|
{
|
|
*first = item->next;
|
|
}
|
|
}
|
|
item->next->prev = item->prev;
|
|
item->prev->next = item->next;
|
|
item->prev = item->next = item;
|
|
}
|
|
|
|
+ (NSArray*) allInstances
|
|
{
|
|
NSArray *a;
|
|
|
|
[allCachesLock lock];
|
|
a = NSAllHashTableObjects(allCaches);
|
|
[allCachesLock unlock];
|
|
return a;
|
|
}
|
|
|
|
+ (id) alloc
|
|
{
|
|
return [self allocWithZone: NSDefaultMallocZone()];
|
|
}
|
|
|
|
+ (id) allocWithZone: (NSZone*)z
|
|
{
|
|
GSCache *c;
|
|
|
|
c = (GSCache*)NSAllocateObject(self, sizeof(Item), z);
|
|
return c;
|
|
}
|
|
|
|
+ (NSString*) description
|
|
{
|
|
NSMutableString *ms;
|
|
NSHashEnumerator e;
|
|
GSCache *c;
|
|
|
|
ms = [NSMutableString stringWithString: [super description]];
|
|
[allCachesLock lock];
|
|
e = NSEnumerateHashTable(allCaches);
|
|
while ((c = (GSCache*)NSNextHashEnumeratorItem(&e)) != nil)
|
|
{
|
|
[ms appendFormat: @"\n%@", [c description]];
|
|
}
|
|
NSEndHashTableEnumeration(&e);
|
|
[allCachesLock unlock];
|
|
return ms;
|
|
}
|
|
|
|
+ (void) initialize
|
|
{
|
|
if (allCaches == 0)
|
|
{
|
|
itemOffset = class_getInstanceSize(self);
|
|
allCaches
|
|
= NSCreateHashTable(NSNonRetainedObjectHashCallBacks, 0);
|
|
if ([NSThread isMultiThreaded] == YES)
|
|
{
|
|
[self _becomeThreaded: nil];
|
|
}
|
|
else
|
|
{
|
|
/* If and when we become multi-threaded, the +_becomeThreaded:
|
|
* method will remove us as an observer and will create a lock
|
|
* for the table of all caches, then ask each cache to create
|
|
* its own lock.
|
|
*/
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserver: self
|
|
selector: @selector(_becomeThreaded:)
|
|
name: NSWillBecomeMultiThreadedNotification
|
|
object: nil];
|
|
}
|
|
GSTickerTimeNow();
|
|
}
|
|
}
|
|
|
|
- (unsigned) currentObjects
|
|
{
|
|
return my->currentObjects;
|
|
}
|
|
|
|
- (NSUInteger) currentSize
|
|
{
|
|
return my->currentSize;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
[allCachesLock lock];
|
|
NSHashRemove(allCaches, (void*)self);
|
|
[allCachesLock unlock];
|
|
if (my->contents != 0)
|
|
{
|
|
[self shrinkObjects: 0 andSize: 0];
|
|
NSFreeMapTable(my->contents);
|
|
}
|
|
[my->exclude release];
|
|
[my->name release];
|
|
[my->lock release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (id) delegate
|
|
{
|
|
return my->delegate;
|
|
}
|
|
|
|
- (NSString*) description
|
|
{
|
|
NSString *n;
|
|
|
|
[my->lock lock];
|
|
n = my->name;
|
|
if (n == nil)
|
|
{
|
|
n = [super description];
|
|
}
|
|
n = [NSString stringWithFormat:
|
|
@" %@\n"
|
|
@" Items: %u(%u)\n"
|
|
@" Size: %"PRIuPTR"(%"PRIuPTR")\n"
|
|
@" Life: %u\n"
|
|
@" Hit: %u\n"
|
|
@" Miss: %u\n",
|
|
n,
|
|
my->currentObjects, my->maxObjects,
|
|
my->currentSize, my->maxSize,
|
|
my->lifetime,
|
|
my->hits,
|
|
my->misses];
|
|
[my->lock unlock];
|
|
return n;
|
|
}
|
|
|
|
- (id) init
|
|
{
|
|
if (nil != (self = [super init]))
|
|
{
|
|
if ([NSThread isMultiThreaded] == YES)
|
|
{
|
|
[self _createLock];
|
|
}
|
|
my->contents = NSCreateMapTable(NSObjectMapKeyCallBacks,
|
|
NSObjectMapValueCallBacks, 0);
|
|
[allCachesLock lock];
|
|
NSHashInsert(allCaches, (void*)self);
|
|
[allCachesLock unlock];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (unsigned) lifetime
|
|
{
|
|
return my->lifetime;
|
|
}
|
|
|
|
- (unsigned) maxObjects
|
|
{
|
|
return my->maxObjects;
|
|
}
|
|
|
|
- (NSUInteger) maxSize
|
|
{
|
|
return my->maxSize;
|
|
}
|
|
|
|
- (NSString*) name
|
|
{
|
|
NSString *n;
|
|
|
|
[my->lock lock];
|
|
n = [my->name retain];
|
|
[my->lock unlock];
|
|
return [n autorelease];
|
|
}
|
|
|
|
- (id) objectForKey: (id)aKey
|
|
{
|
|
id object;
|
|
GSCacheItem *item;
|
|
unsigned when = GSTickerTimeTick();
|
|
|
|
[my->lock lock];
|
|
item = (GSCacheItem*)NSMapGet(my->contents, aKey);
|
|
if (item == nil)
|
|
{
|
|
my->misses++;
|
|
[my->lock unlock];
|
|
return nil;
|
|
}
|
|
if (item->when > 0 && item->when < when)
|
|
{
|
|
BOOL keep = NO;
|
|
|
|
if (0 != my->replace)
|
|
{
|
|
GSCacheItem *orig = [item retain];
|
|
|
|
[my->lock unlock];
|
|
NS_DURING
|
|
{
|
|
keep = (*(my->replace))(my->delegate,
|
|
@selector(shouldKeepItem:withKey:lifetime:after:),
|
|
item->object,
|
|
aKey,
|
|
item->life,
|
|
when - item->when);
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
[my->lock unlock];
|
|
[localException raise];
|
|
}
|
|
NS_ENDHANDLER
|
|
[my->lock lock];
|
|
if (keep == YES)
|
|
{
|
|
GSCacheItem *current;
|
|
|
|
/* Refetch in case delegate changed it.
|
|
*/
|
|
current = (GSCacheItem*)NSMapGet(my->contents, aKey);
|
|
if (current == nil)
|
|
{
|
|
/* Delegate must have deleted the item even though
|
|
* it returned YES to say we should keep it ...
|
|
* we count this as a miss.
|
|
*/
|
|
my->misses++;
|
|
[my->lock unlock];
|
|
[orig release];
|
|
return nil;
|
|
}
|
|
else if (orig == current)
|
|
{
|
|
/* Delegate told us to keep the original item so we
|
|
* update its expiry time.
|
|
*/
|
|
item->when = when + item->life;
|
|
item->warn = when + item->life / 2;
|
|
}
|
|
else
|
|
{
|
|
/* Delegate replaced the item with another and told
|
|
* us to keep that one.
|
|
*/
|
|
item = current;
|
|
}
|
|
}
|
|
[orig release];
|
|
}
|
|
|
|
if (keep == NO)
|
|
{
|
|
removeItem(item, &my->first);
|
|
my->currentObjects--;
|
|
if (my->maxSize > 0)
|
|
{
|
|
my->currentSize -= item->size;
|
|
}
|
|
NSMapRemove(my->contents, (void*)item->key);
|
|
my->misses++;
|
|
[my->lock unlock];
|
|
return nil; // Lifetime expired.
|
|
}
|
|
}
|
|
else if (item->warn > 0 && item->warn < when)
|
|
{
|
|
item->warn = 0; // Don't warn again.
|
|
if (0 != my->refresh)
|
|
{
|
|
GSCacheItem *orig = [item retain];
|
|
GSCacheItem *current;
|
|
|
|
[my->lock unlock];
|
|
NS_DURING
|
|
{
|
|
(*(my->refresh))(my->delegate,
|
|
@selector(mayRefreshItem:withKey:lifetime:after:),
|
|
item->object,
|
|
aKey,
|
|
item->life,
|
|
when - item->when);
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
[my->lock unlock];
|
|
[localException raise];
|
|
}
|
|
NS_ENDHANDLER
|
|
[my->lock lock];
|
|
|
|
/* Refetch in case delegate changed it.
|
|
*/
|
|
current = (GSCacheItem*)NSMapGet(my->contents, aKey);
|
|
if (current == nil)
|
|
{
|
|
/* Delegate must have deleted the item!
|
|
* So we count this as a miss.
|
|
*/
|
|
my->misses++;
|
|
[my->lock unlock];
|
|
[orig release];
|
|
return nil;
|
|
}
|
|
else
|
|
{
|
|
item = current;
|
|
}
|
|
[orig release];
|
|
}
|
|
}
|
|
|
|
// Least recently used ... move to end of list.
|
|
removeItem(item, &my->first);
|
|
appendItem(item, &my->first);
|
|
my->hits++;
|
|
object = [item->object retain];
|
|
[my->lock unlock];
|
|
return [object autorelease];
|
|
}
|
|
|
|
- (void) purge
|
|
{
|
|
if (my->contents != 0)
|
|
{
|
|
unsigned when = GSTickerTimeTick();
|
|
NSMapEnumerator e;
|
|
GSCacheItem *i;
|
|
id k;
|
|
|
|
[my->lock lock];
|
|
e = NSEnumerateMapTable(my->contents);
|
|
while (NSNextMapEnumeratorPair(&e, (void**)&k, (void**)&i) != 0)
|
|
{
|
|
if (i->when > 0 && i->when < when)
|
|
{
|
|
removeItem(i, &my->first);
|
|
my->currentObjects--;
|
|
if (my->maxSize > 0)
|
|
{
|
|
my->currentSize -= i->size;
|
|
}
|
|
NSMapRemove(my->contents, (void*)i->key);
|
|
}
|
|
}
|
|
NSEndMapTableEnumeration(&e);
|
|
[my->lock unlock];
|
|
}
|
|
}
|
|
|
|
- (oneway void) release
|
|
{
|
|
/* We lock the table while checking, to prevent
|
|
* another thread from grabbing this object while we are
|
|
* checking it.
|
|
* If we are going to deallocate the object, we first remove
|
|
* it from the table so that no other thread will find it
|
|
* and try to use it while it is being deallocated.
|
|
*/
|
|
[allCachesLock lock];
|
|
if (NSDecrementExtraRefCountWasZero(self))
|
|
{
|
|
NSHashRemove(allCaches, (void*)self);
|
|
[allCachesLock unlock];
|
|
[self dealloc];
|
|
}
|
|
else
|
|
{
|
|
[allCachesLock unlock];
|
|
}
|
|
}
|
|
|
|
- (void) setDelegate: (id)anObject
|
|
{
|
|
[my->lock lock];
|
|
my->delegate = anObject;
|
|
if ([my->delegate respondsToSelector:
|
|
@selector(shouldKeepItem:withKey:lifetime:after:)])
|
|
{
|
|
my->replace = (BOOL (*)(id,SEL,id,id,unsigned,unsigned))
|
|
[my->delegate methodForSelector:
|
|
@selector(shouldKeepItem:withKey:lifetime:after:)];
|
|
}
|
|
else
|
|
{
|
|
my->replace = 0;
|
|
}
|
|
if ([my->delegate respondsToSelector:
|
|
@selector(mayRefreshItem:withKey:lifetime:after:)])
|
|
{
|
|
my->refresh = (void (*)(id,SEL,id,id,unsigned,unsigned))
|
|
[my->delegate methodForSelector:
|
|
@selector(mayRefreshItem:withKey:lifetime:after:)];
|
|
}
|
|
else
|
|
{
|
|
my->refresh = 0;
|
|
}
|
|
[my->lock unlock];
|
|
}
|
|
|
|
- (void) setLifetime: (unsigned)max
|
|
{
|
|
my->lifetime = max;
|
|
}
|
|
|
|
- (void) setMaxObjects: (unsigned)max
|
|
{
|
|
[my->lock lock];
|
|
my->maxObjects = max;
|
|
if (my->currentObjects > my->maxObjects)
|
|
{
|
|
[self shrinkObjects: my->maxObjects
|
|
andSize: my->maxSize];
|
|
}
|
|
[my->lock unlock];
|
|
}
|
|
|
|
- (void) setMaxSize: (NSUInteger)max
|
|
{
|
|
[my->lock lock];
|
|
if (max > 0 && my->maxSize == 0)
|
|
{
|
|
NSMapEnumerator e = NSEnumerateMapTable(my->contents);
|
|
GSCacheItem *i;
|
|
id k;
|
|
NSUInteger size = 0;
|
|
|
|
if (my->exclude == nil)
|
|
{
|
|
my->exclude = [NSMutableSet new];
|
|
}
|
|
while (NSNextMapEnumeratorPair(&e, (void**)&k, (void**)&i) != 0)
|
|
{
|
|
if (i->size == 0)
|
|
{
|
|
[my->exclude removeAllObjects];
|
|
i->size = [i->object sizeInBytes: my->exclude];
|
|
}
|
|
if (i->size > max)
|
|
{
|
|
/*
|
|
* Item in cache is too big for new size limit ...
|
|
* Remove it.
|
|
*/
|
|
removeItem(i, &my->first);
|
|
NSMapRemove(my->contents, (void*)i->key);
|
|
my->currentObjects--;
|
|
continue;
|
|
}
|
|
size += i->size;
|
|
}
|
|
NSEndMapTableEnumeration(&e);
|
|
my->currentSize = size;
|
|
}
|
|
else if (max == 0)
|
|
{
|
|
my->currentSize = 0;
|
|
}
|
|
my->maxSize = max;
|
|
if (my->currentSize > my->maxSize)
|
|
{
|
|
[self shrinkObjects: my->maxObjects
|
|
andSize: my->maxSize];
|
|
}
|
|
[my->lock unlock];
|
|
}
|
|
|
|
- (void) setName: (NSString*)name
|
|
{
|
|
[my->lock lock];
|
|
[name retain];
|
|
[my->name release];
|
|
my->name = name;
|
|
[my->lock unlock];
|
|
}
|
|
|
|
- (void) setObject: (id)anObject forKey: (id)aKey
|
|
{
|
|
[self setObject: anObject forKey: aKey lifetime: my->lifetime];
|
|
}
|
|
|
|
- (void) setObject: (id)anObject
|
|
forKey: (id)aKey
|
|
lifetime: (unsigned)lifetime
|
|
{
|
|
GSCacheItem *item;
|
|
unsigned maxObjects;
|
|
NSUInteger maxSize;
|
|
unsigned addObjects = (anObject == nil ? 0 : 1);
|
|
NSUInteger addSize = 0;
|
|
|
|
[my->lock lock];
|
|
maxObjects = my->maxObjects;
|
|
maxSize = my->maxSize;
|
|
item = (GSCacheItem*)NSMapGet(my->contents, aKey);
|
|
if (item != nil)
|
|
{
|
|
removeItem(item, &my->first);
|
|
my->currentObjects--;
|
|
if (my->maxSize > 0)
|
|
{
|
|
my->currentSize -= item->size;
|
|
}
|
|
NSMapRemove(my->contents, (void*)aKey);
|
|
}
|
|
|
|
if (addObjects > 0 && (maxSize > 0 || maxObjects > 0))
|
|
{
|
|
if (maxSize > 0)
|
|
{
|
|
if (my->exclude == nil)
|
|
{
|
|
my->exclude = [NSMutableSet new];
|
|
}
|
|
[my->exclude removeAllObjects];
|
|
addSize = [anObject sizeInBytes: my->exclude];
|
|
if (addSize > maxSize)
|
|
{
|
|
addObjects = 0; // Object too big to cache.
|
|
}
|
|
}
|
|
}
|
|
|
|
if (addObjects > 0)
|
|
{
|
|
/*
|
|
* Make room for new object.
|
|
*/
|
|
[self shrinkObjects: maxObjects - addObjects
|
|
andSize: maxSize - addSize];
|
|
item = [GSCacheItem newWithObject: anObject forKey: aKey];
|
|
if (lifetime > 0)
|
|
{
|
|
unsigned tick = GSTickerTimeTick();
|
|
|
|
item->when = tick + lifetime;
|
|
item->warn = tick + lifetime / 2;
|
|
}
|
|
item->life = lifetime;
|
|
item->size = addSize;
|
|
NSMapInsert(my->contents, (void*)item->key, (void*)item);
|
|
appendItem(item, &my->first);
|
|
my->currentObjects += addObjects;
|
|
my->currentSize += addSize;
|
|
[item release];
|
|
}
|
|
[my->lock unlock];
|
|
}
|
|
|
|
- (void) setObject: (id)anObject
|
|
forKey: (id)aKey
|
|
until: (NSDate*)expires
|
|
{
|
|
NSTimeInterval i;
|
|
|
|
i = (expires == nil) ? 0.0 : [expires timeIntervalSinceReferenceDate];
|
|
i -= GSTickerTimeNow();
|
|
if (i <= 0.0)
|
|
{
|
|
[self setObject: nil forKey: aKey]; // Already expired
|
|
}
|
|
else
|
|
{
|
|
unsigned limit;
|
|
|
|
if (i > 2415919103.0)
|
|
{
|
|
limit = 0; // Limit in far future.
|
|
}
|
|
else
|
|
{
|
|
limit = (unsigned)i;
|
|
}
|
|
[self setObject: anObject
|
|
forKey: aKey
|
|
lifetime: limit];
|
|
}
|
|
}
|
|
|
|
- (void) shrinkObjects: (unsigned)objects andSize: (NSUInteger)size
|
|
{
|
|
NSUInteger newSize;
|
|
unsigned newObjects;
|
|
|
|
[my->lock lock];
|
|
newSize = [self currentSize];
|
|
newObjects = [self currentObjects];
|
|
if (newObjects > objects || (my->maxSize > 0 && newSize > size))
|
|
{
|
|
[self purge];
|
|
newSize = [self currentSize];
|
|
newObjects = [self currentObjects];
|
|
while (newObjects > objects || (my->maxSize > 0 && newSize > size))
|
|
{
|
|
GSCacheItem *item;
|
|
|
|
item = my->first;
|
|
removeItem(item, &my->first);
|
|
newObjects--;
|
|
if (my->maxSize > 0)
|
|
{
|
|
newSize -= item->size;
|
|
}
|
|
NSMapRemove(my->contents, (void*)item->key);
|
|
}
|
|
my->currentObjects = newObjects;
|
|
my->currentSize = newSize;
|
|
}
|
|
[my->lock unlock];
|
|
}
|
|
@end
|
|
@implementation GSCache (Threading)
|
|
+ (void) _becomeThreaded: (NSNotification*)n
|
|
{
|
|
NSHashEnumerator e;
|
|
GSCache *c;
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver: self
|
|
name: NSWillBecomeMultiThreadedNotification object: nil];
|
|
allCachesLock = [NSRecursiveLock new];
|
|
e = NSEnumerateHashTable(allCaches);
|
|
while ((c = (GSCache*)NSNextHashEnumeratorItem(&e)) != nil)
|
|
{
|
|
[c _createLock];
|
|
}
|
|
NSEndHashTableEnumeration(&e);
|
|
}
|
|
- (void) _createLock
|
|
{
|
|
my->lock = [NSRecursiveLock new];
|
|
}
|
|
@end
|
|
|
|
@implementation NSArray (GSCacheSizeInBytes)
|
|
- (NSUInteger) sizeInBytes: (NSMutableSet*)exclude
|
|
{
|
|
NSUInteger size = [super sizeInBytes: exclude];
|
|
|
|
if (size > 0)
|
|
{
|
|
NSUInteger count = [self count];
|
|
|
|
size += count*sizeof(void*);
|
|
while (count-- > 0)
|
|
{
|
|
size += [[self objectAtIndex: count] sizeInBytes: exclude];
|
|
}
|
|
}
|
|
return size;
|
|
}
|
|
@end
|
|
|
|
@implementation NSData (GSCacheSizeInBytes)
|
|
- (NSUInteger) sizeInBytes: (NSMutableSet*)exclude
|
|
{
|
|
NSUInteger size = [super sizeInBytes: exclude];
|
|
|
|
if (size > 0)
|
|
{
|
|
size += [self length];
|
|
}
|
|
return size;
|
|
}
|
|
@end
|
|
|
|
@implementation NSDictionary (GSCacheSizeInBytes)
|
|
- (NSUInteger) sizeInBytes: (NSMutableSet*)exclude
|
|
{
|
|
NSUInteger size = [super sizeInBytes: exclude];
|
|
|
|
if (size > 0)
|
|
{
|
|
NSUInteger count = [self count];
|
|
|
|
size += 3 * sizeof(void*) * count;
|
|
if (count > 0)
|
|
{
|
|
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
|
NSEnumerator *enumerator = [self keyEnumerator];
|
|
NSObject *k;
|
|
|
|
while ((k = [enumerator nextObject]) != nil)
|
|
{
|
|
NSObject *o = [self objectForKey: k];
|
|
|
|
size += [k sizeInBytes: exclude] + [o sizeInBytes: exclude];
|
|
}
|
|
[pool release];
|
|
}
|
|
}
|
|
return size;
|
|
}
|
|
@end
|
|
|
|
@implementation NSObject (GSCacheSizeInBytes)
|
|
- (NSUInteger) sizeInBytes: (NSMutableSet*)exclude
|
|
{
|
|
if ([exclude member: self] != nil)
|
|
{
|
|
return 0;
|
|
}
|
|
[exclude addObject: self];
|
|
|
|
return class_getInstanceSize(object_getClass(self));
|
|
}
|
|
@end
|
|
|
|
@implementation NSSet (GSCacheSizeInBytes)
|
|
- (NSUInteger) sizeInBytes: (NSMutableSet*)exclude
|
|
{
|
|
NSUInteger size = [super sizeInBytes: exclude];
|
|
|
|
if (size > 0)
|
|
{
|
|
NSUInteger count = [self count];
|
|
|
|
size += 3 * sizeof(void*) * count;
|
|
if (count > 0)
|
|
{
|
|
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
|
NSEnumerator *enumerator = [self objectEnumerator];
|
|
NSObject *o;
|
|
|
|
while ((o = [enumerator nextObject]) != nil)
|
|
{
|
|
size += [o sizeInBytes: exclude];
|
|
}
|
|
[pool release];
|
|
}
|
|
}
|
|
return size;
|
|
}
|
|
@end
|
|
|
|
@implementation NSString (GSCacheSizeInBytes)
|
|
- (NSUInteger) sizeInBytes: (NSMutableSet*)exclude
|
|
{
|
|
if ([exclude member: self] != nil)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return [super sizeInBytes: exclude] + sizeof(unichar) * [self length];
|
|
}
|
|
}
|
|
@end
|
|
|
|
#if defined(GNUSTEP_BASE_LIBRARY)
|
|
|
|
// FIXME ... this should be moved to base additions.
|
|
#import <GNUstepBase/GSMime.h>
|
|
|
|
#if GS_NONFRAGILE
|
|
/* When using the non-fragile ABI, base does not expose the mime class
|
|
* instance variables, so we use the raw data size as a rough approximation.
|
|
* The actual memory usage will be larger than this of course.
|
|
*/
|
|
@implementation GSMimeDocument (GSCacheSizeInBytes)
|
|
- (NSUInteger) sizeInBytes: (NSMutableSet*)exclude
|
|
{
|
|
return [[self rawMimeData] sizeInBytes: exclude];
|
|
}
|
|
@end
|
|
|
|
@implementation GSMimeHeader (GSCacheSizeInBytes)
|
|
- (NSUInteger) sizeInBytes: (NSMutableSet*)exclude
|
|
{
|
|
return [[self rawMimeData] sizeInBytes: exclude];
|
|
}
|
|
@end
|
|
#else
|
|
@implementation GSMimeDocument (GSCacheSizeInBytes)
|
|
- (NSUInteger) sizeInBytes: (NSMutableSet*)exclude
|
|
{
|
|
NSUInteger size = [super sizeInBytes: exclude];
|
|
|
|
if (size > 0)
|
|
{
|
|
size += [content sizeInBytes: exclude] + [headers sizeInBytes: exclude];
|
|
}
|
|
return size;
|
|
}
|
|
@end
|
|
|
|
@implementation GSMimeHeader (GSCacheSizeInBytes)
|
|
- (NSUInteger) sizeInBytes: (NSMutableSet*)exclude
|
|
{
|
|
NSUInteger size = [super sizeInBytes: exclude];
|
|
|
|
if (size > 0)
|
|
{
|
|
size += [name sizeInBytes: exclude]
|
|
+ [value sizeInBytes: exclude]
|
|
+ [objects sizeInBytes: exclude]
|
|
+ [params sizeInBytes: exclude];
|
|
}
|
|
return size;
|
|
}
|
|
@end
|
|
#endif
|
|
|
|
#endif
|
|
|