diff --git a/ChangeLog b/ChangeLog index 2dbb57bc6..85788e771 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +2009-01-20 Richard Frith-Macdonald + + * Source/NSKeyValueObserving.m: Automtically remove collected observers + In a garbage collecting environment. + * Headers/Foundation/NSZone.h: + * Source/NSZone.m: + Add new functions for handling weak pointers. This allows all code + which uses the boehm garbage collector header to be localised in + NSZone.m and NSGarbageCollector.m + * Source/NSNotificationCenter.m: + Update to use new functions. + * Source/Additions/GSInsensitiveDictionary.m: + Fix to use scanned memory in a garbage collecting environment. + 2009-01-19 Richard Frith-Macdonald * configure.ac: Add options from pathconfig diff --git a/Headers/Foundation/NSZone.h b/Headers/Foundation/NSZone.h index a7338e291..88f8bd9c8 100644 --- a/Headers/Foundation/NSZone.h +++ b/Headers/Foundation/NSZone.h @@ -62,37 +62,6 @@ typedef struct _NSZone NSZone; extern "C" { #endif - -/** - * NSZoneStats is the structure returned by the NSZoneStats() - * function that summarizes the current usage of a zone. It is similar to - * the structure mstats in the GNU C library. It has 5 fields of - * type size_t- - * - * bytes_total - * - * This is the total size of memory managed by the zone, in bytes. - * chunks_used - * This is the number of memory chunks in use in the zone. - * bytes_used - * This is the number of bytes in use. - * chunks_free - * This is the number of memory chunks that are not in use. - * bytes_free - * - * This is the number of bytes managed by the zone that are not in use. - * - * - */ -struct NSZoneStats -{ - size_t bytes_total; - size_t chunks_used; - size_t bytes_used; - size_t chunks_free; - size_t bytes_free; -}; - /** * Primary structure representing an NSZone. Technically it * consists of a set of function pointers for zone upkeep functions plus some @@ -131,35 +100,13 @@ struct _NSZone NSZone *next; }; -/** - * Try to get more memory - the normal process has failed. - * If we can't do anything, just return a null pointer. - * Try to do some logging if possible. - */ -void* -GSOutOfMemory(size_t size, BOOL retry); - -NSZone* +GS_EXPORT NSZone* NSCreateZone (size_t start, size_t gran, BOOL canFree); -NSZone* +GS_EXPORT NSZone* NSDefaultMallocZone (void); -/** - * Returns the default zone used for memory allocation, created at startup. - * This zone cannot be recycled. - */ -NSZone* -GSAtomicMallocZone (void); - -/** - * Returns the default zone used for scanned memory allocation ... a - * garbage collectable chunk of memory which is scanned for pointers. - */ -NSZone* -GSScannedMallocZone (void); - -NSZone* +GS_EXPORT NSZone* NSZoneFromPointer (void *ptr); /** @@ -169,7 +116,7 @@ NSZoneFromPointer (void *ptr); * allocate and no more can be obtained from system, unless using the * default zone, in which case NULL is returned. */ -void* +GS_EXPORT void* NSZoneMalloc (NSZone *zone, size_t size); /** @@ -179,7 +126,7 @@ NSZoneMalloc (NSZone *zone, size_t size); * allocate and no more can be obtained from system, unless using the * default zone, in which case NULL is returned. */ -void* +GS_EXPORT void* NSZoneCalloc (NSZone *zone, size_t elems, size_t bytes); /** @@ -189,7 +136,7 @@ NSZoneCalloc (NSZone *zone, size_t elems, size_t bytes); * zone and no more memory can be obtained from the system, unless using the * default zone, in which case NULL is returned. */ -void* +GS_EXPORT void* NSZoneRealloc (NSZone *zone, void *ptr, size_t size); /** @@ -199,7 +146,7 @@ NSZoneRealloc (NSZone *zone, void *ptr, size_t size); * must simply equal the number of allocation calls. The default zone, on the * other hand, cannot be recycled. */ -void +GS_EXPORT void NSRecycleZone (NSZone *zone); /** @@ -208,36 +155,121 @@ NSRecycleZone (NSZone *zone); * returns it to zone. Note, if this is a nonfreeable zone, the memory is * not actually freed, but the count of number of free()s is updated. */ -void +GS_EXPORT void NSZoneFree (NSZone *zone, void *ptr); /** * Sets name of the given zone (useful for debugging and logging). */ -void +GS_EXPORT void NSSetZoneName (NSZone *zone, NSString *name); /** * Sets name of the given zone (useful for debugging and logging). */ -NSString* +GS_EXPORT NSString* NSZoneName (NSZone *zone); #if OS_API_VERSION(GS_API_NONE, GS_API_NONE) -/** +/** Deprecated ...
* Checks integrity of a zone. Not defined by OpenStep or OS X. */ BOOL NSZoneCheck (NSZone *zone); /** + * NSZoneStats is the structure returned by the NSZoneStats() + * function that summarizes the current usage of a zone. It is similar to + * the structure mstats in the GNU C library. It has 5 fields of + * type size_t- + * + * bytes_total + * + * This is the total size of memory managed by the zone, in bytes. + * chunks_used + * This is the number of memory chunks in use in the zone. + * bytes_used + * This is the number of bytes in use. + * chunks_free + * This is the number of memory chunks that are not in use. + * bytes_free + * + * This is the number of bytes managed by the zone that are not in use. + * + * + */ +struct NSZoneStats +{ + size_t bytes_total; + size_t chunks_used; + size_t bytes_used; + size_t chunks_free; + size_t bytes_free; +}; + +/** Deprecated ...
* Obtain statistics about the zone. Implementation emphasis is on * correctness, not speed. Not defined by OpenStep or OS X. */ struct NSZoneStats NSZoneStats (NSZone *zone); +/** + * Try to get more memory - the normal process has failed. + * If we can't do anything, just return a null pointer. + * Try to do some logging if possible. + */ +void* +GSOutOfMemory(size_t size, BOOL retry); + +/** + * Returns the default zone used for memory allocation, created at startup. + * This zone cannot be recycled. + */ +GS_EXPORT NSZone* +GSAtomicMallocZone (void); + +/** + * Returns the default zone used for scanned memory allocation ... a + * garbage collectable chunk of memory which is scanned for pointers. + */ +GS_EXPORT NSZone* +GSScannedMallocZone (void); + +/** + * Called during +initialize to tell the class that instances created + * in future should have the specified instance variable as a weak + * pointer for garbage collection.
+ * NB. making a pointer weak does not mean that it is automatically + * zeroed when the object it points to is garbage collected. To get that + * behavior you must asign values to the pointer using the + * GSAssignZeroingWeakPointer() function. + * This function has no effect if the system is + * not built for garbage collection. + */ +GS_EXPORT void +GSMakeWeakPointer(Class class, const char *iVarName); + +/** + * This function must be used to assign a value to a zeroing weak pointer.
+ * A zeroing weak pointer is one where, when the garbage collector collects + * the object pointed to, it also clears the weak pointer.
+ * Assigning zero (nil) will always succeed and has the effect of telling the + * garbage collector that it no longer needs to track the previously assigned + * object. Apart from that case, a source needs to be garbage collectable for + * this function to work, and using a non-garbage collectable value will + * cause the function to return NO.
+ * The destination object (watching the source object) must also be memory + * allocated by the garbage colleector, and if it is not the function will + * return NO.
+ * If garbage collection is not in use, this function performs a simple + * assignment returning YES, unless destination is null in which case it + * returns NO. + */ +GS_EXPORT BOOL +GSAssignZeroingWeakPointer(void **destination, void *source); + #endif GS_EXPORT NSUInteger diff --git a/Source/Additions/GSInsensitiveDictionary.m b/Source/Additions/GSInsensitiveDictionary.m index 45cf65af1..22b3a3dfd 100644 --- a/Source/Additions/GSInsensitiveDictionary.m +++ b/Source/Additions/GSInsensitiveDictionary.m @@ -159,7 +159,11 @@ static SEL objSel; [aCoder decodeValueOfObjCType: @encode(unsigned) at: &count]; +#if GS_WITH_GC + GSIMapInitWithZoneAndCapacity(&map, GSScannedMallocZone(), count); +#else GSIMapInitWithZoneAndCapacity(&map, GSObjCZone(self), count); +#endif while (count-- > 0) { (*imp)(aCoder, sel, type, &key); @@ -175,7 +179,12 @@ static SEL objSel; { unsigned int i; +#if GS_WITH_GC + GSIMapInitWithZoneAndCapacity(&map, GSScannedMallocZone(), c); +#else GSIMapInitWithZoneAndCapacity(&map, GSObjCZone(self), c); +#endif + for (i = 0; i < c; i++) { GSIMapNode node; @@ -214,9 +223,14 @@ static SEL objSel; - (id) initWithDictionary: (NSDictionary*)other copyItems: (BOOL)shouldCopy { - NSZone *z = GSObjCZone(self); + NSZone *z; unsigned c = [other count]; +#if GS_WITH_GC + z = GSScannedMallocZone(); +#else + z = GSObjCZone(self); +#endif GSIMapInitWithZoneAndCapacity(&map, z, c); if (c > 0) @@ -369,7 +383,11 @@ static SEL objSel; /* Designated initialiser */ - (id) initWithCapacity: (unsigned)cap { +#if GS_WITH_GC + GSIMapInitWithZoneAndCapacity(&map, GSScannedMallocZone(), cap); +#else GSIMapInitWithZoneAndCapacity(&map, GSObjCZone(self), cap); +#endif return self; } diff --git a/Source/NSKeyValueObserving.m b/Source/NSKeyValueObserving.m index c6f4566f1..682ec81ca 100644 --- a/Source/NSKeyValueObserving.m +++ b/Source/NSKeyValueObserving.m @@ -138,7 +138,7 @@ static inline void setup() @interface GSKVOObservation : NSObject { @public - NSObject *observer; // Not retained + NSObject *observer; // Not retained (zeroing weak pointer) void *context; int options; } @@ -586,7 +586,8 @@ replacementForClass(Class c) if (!found) { - NSDebugLLog(@"KVC", @"class %@ not KVC complient for %@", original, aKey); + NSDebugLLog(@"KVC", @"class %@ not KVC complient for %@", + original, aKey); /* [NSException raise: NSInvalidArgumentException format: @"class not KVC complient for %@", aKey]; @@ -804,6 +805,12 @@ replacementForClass(Class c) @implementation GSKVOObservation +#if GS_WITH_GC ++ (void) initialize +{ + GSMakeWeakPointer(self, "observer"); +} +#endif @end @implementation GSKVOPathInfo @@ -956,17 +963,26 @@ replacementForClass(Class c) o = [pathInfo->observations objectAtIndex: count]; if (o->observer == anObserver) { - o->observer = anObserver; o->context = aContext; o->options = options; observation = o; } +#if GS_WITH_GC + else if (o->observer == nil) + { + /* The observer for thsi observation must have been collected. + */ + [pathInfo->observations removeObjectAtIndex: count]; + continue; + } +#endif pathInfo->allOptions |= o->options; } if (observation == nil) { observation = [GSKVOObservation new]; - observation->observer = anObserver; + GSAssignZeroingWeakPointer((void**)&observation->observer, + (void*)anObserver); observation->context = aContext; observation->options = options; [pathInfo->observations addObject: observation]; @@ -1050,7 +1066,7 @@ replacementForClass(Class c) GSKVOObservation *o; o = [pathInfo->observations objectAtIndex: count]; - if (o->observer == anObserver) + if (o->observer == anObserver || o->observer == nil) { [pathInfo->observations removeObjectAtIndex: count]; if ([pathInfo->observations count] == 0) @@ -1088,6 +1104,14 @@ replacementForClass(Class c) context = o->context; break; } +#if GS_WITH_GC + else if (o->observer == nil) + { + /* The observer for thsi observation must have been collected. + */ + [pathInfo->observations removeObjectAtIndex: count]; + } +#endif } } [iLock unlock]; diff --git a/Source/NSNotificationCenter.m b/Source/NSNotificationCenter.m index 3f05bf78e..0f15a05f5 100644 --- a/Source/NSNotificationCenter.m +++ b/Source/NSNotificationCenter.m @@ -41,7 +41,6 @@ * a collected observer removed. */ #if GS_WITH_GC -#include #define purgeCollected(X) (X = listPurge(X, nil)) #else #define purgeCollected(X) (X) @@ -437,7 +436,7 @@ static void obsFree(Observation *o) NCTable *t = o->link; #if GS_WITH_GC - GC_unregister_disappearing_link((GC_PTR*)&o->observer); + GSAssignZeroingWeakPointer((void**)&o->observer, 0); #endif o->link = (NCTable*)t->freeList; t->freeList = o; @@ -705,23 +704,14 @@ static NSNotificationCenter *default_center = nil; o = obsNew(TABLE); o->selector = selector; o->method = method; +#if GS_WITH_GC + GSAssignZeroingWeakPointer((void**)&o->observer, (void*)observer); +#else o->observer = observer; +#endif o->retained = 0; o->next = 0; -#if GS_WITH_GC - /* Ensure that if the observer is garbage collected, we clear the - * oservation so that we don't end up sending notifications to the - * deallocated object. - * The observer must be a real GC-allocated object or this mechanism - * can't be used. - */ - if (GC_base(observer) != 0) - { - GC_general_register_disappearing_link((GC_PTR*)&o->observer, observer); - } -#endif - if (object != nil) { object = CHEATGC(object); diff --git a/Source/NSZone.m b/Source/NSZone.m index ecbdb6e93..ac18a27f9 100644 --- a/Source/NSZone.m +++ b/Source/NSZone.m @@ -1837,14 +1837,8 @@ NSReallocateCollectable(void *ptr, NSUInteger size, NSUInteger options) #define GS_ZONE_ATTR __attribute__((unused)) #endif -#ifndef GS_WITH_GC -#define GS_WITH_GC 0 -#endif #if GS_WITH_GC -#include - - NSZone* NSCreateZone (size_t start, size_t gran, BOOL canFree) { @@ -1875,76 +1869,11 @@ NSZoneFromPointer (void *ptr) return &default_zone; } -void* -NSZoneMalloc (NSZone *zone, size_t size) -{ - void *ptr; - - if (zone == GSAtomicMallocZone()) - ptr = (void*)GC_MALLOC_ATOMIC(size); - else if (zone == GSScannedMallocZone()) - ptr = (void*)GC_MALLOC(size); - else - ptr = (void*)malloc(size); - - if (ptr == 0) - ptr = GSOutOfMemory(size, YES); - return ptr; -} - -void* -NSZoneCalloc (NSZone *zone, size_t elems, size_t bytes) -{ - size_t size = elems * bytes; - void *ptr; - - if (zone == &atomic_zone) - ptr = (void*)GC_MALLOC_ATOMIC(size); - else if (zone == &scanned_zone) - ptr = (void*)GC_MALLOC(size); - else - ptr = (void*)malloc(size); - - if (ptr == 0) - ptr = GSOutOfMemory(size, NO); - memset(ptr, '\0', size); - return ptr; -} - -void* -NSZoneRealloc (NSZone *zone, void *ptr, size_t size) -{ - if (GC_base(ptr) != 0) - { - ptr = GC_REALLOC(ptr, size); - } - else - { - ptr = realloc(ptr, size); - } - if (ptr == 0) - GSOutOfMemory(size, NO); - return ptr; -} - void NSRecycleZone (NSZone *zone) { } -void -NSZoneFree (NSZone *zone, void *ptr) -{ - if (GC_base(ptr) != 0) - { - GC_FREE(ptr); - } - else - { - free(ptr); - } -} - void NSSetZoneName (NSZone *zone, NSString *name) { @@ -1969,6 +1898,101 @@ NSZoneStats NSZoneStats (NSZone *zone) return stats; } +void +GSMakeWeakPointer(Class class, const char *iVarName) +{ + class_ivar_set_gcinvisible(class, iVarName, YES); +} + + +#include + +BOOL +GSAssignZeroingWeakPointer(void **destination, void *source) +{ + if (GC_base(destination) == 0) + { + return NO; // Destination is not in garbage collection system. + } + if (*destination == source) + { + return YES; // Already assigned. + } + if (source != 0 && GC_base(source) == 0) + { + return NO; // Source is not garbage collectable. + } + if (*destination != 0) + { + GC_unregister_disappearing_link((GC_PTR*)destination); + } + *destination = source; + GC_general_register_disappearing_link((GC_PTR*)destination, source); + return YES; +} + +void* +NSZoneMalloc (NSZone *zone, size_t size) +{ + return NSZoneCalloc(zone, 1, size); +} + +void* +NSZoneCalloc (NSZone *zone, size_t elems, size_t bytes) +{ + size_t size = elems * bytes; + void *ptr; + + if (zone == &atomic_zone) + { + ptr = (void*)GC_MALLOC_ATOMIC(size); + } + else if (zone == &scanned_zone) + { + ptr = (void*)GC_MALLOC(size); + } + else + { + ptr = (void*)malloc(size); + if (ptr == 0) + { + ptr = GSOutOfMemory(size, NO); + } + if (ptr != 0) + { + memset(ptr, '\0', size); + } + } + return ptr; +} + +void* +NSZoneRealloc (NSZone *zone, void *ptr, size_t size) +{ + if (GC_base(ptr) != 0) + { + ptr = GC_REALLOC(ptr, size); + } + else + { + ptr = realloc(ptr, size); + } + return ptr; +} + +void +NSZoneFree (NSZone *zone, void *ptr) +{ + if (GC_base(ptr) != 0) + { + GC_FREE(ptr); + } + else + { + free(ptr); + } +} + #else /* GS_WITH_GC */ NSZone* @@ -1989,6 +2013,23 @@ GSScannedMallocZone (void) return &default_zone; } +void +GSMakeWeakPointer(Class class, const char *iVarName) +{ + return; +} + +BOOL +GSAssignZeroingWeakPointer(void **destination, void *source) +{ + if (destination == 0) + { + return NO; + } + *destination = source; + return YES; +} + void* NSZoneMalloc (NSZone *zone, size_t size) {