NotificationCenter removeObserver (#396)

This modifies the NotificationCenter class to do the following:

- add an ivar to the class for an array to hold observers that need to be released when they are removed
- adds an observer to that array if the observer is of class GSNotificationObserver
- upon removing an observer, check if it is in that array, if so remove it from the array and release it.

This replaces the previous implementation which checked the class of the observer as it was being removed, which would cause a crash if the observer had been deallocated.
This commit is contained in:
williameveretteggplant 2024-04-30 10:44:26 -06:00 committed by GitHub
parent 94cf2026b6
commit 21a48f6136
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 25 additions and 7 deletions

View file

@ -74,7 +74,7 @@ GS_EXPORT_CLASS
{
#if GS_EXPOSE(NSNotificationCenter)
@private
void *_table;
void *_table;
#endif
}

View file

@ -36,6 +36,7 @@
#import "Foundation/NSLock.h"
#import "Foundation/NSOperation.h"
#import "Foundation/NSThread.h"
#import "Foundation/NSHashTable.h"
#import "GNUstepBase/GSLock.h"
static NSZone *_zone = 0;
@ -240,6 +241,7 @@ typedef struct NCTbl {
GSIMapTable cache[CACHESIZE];
unsigned short chunkIndex;
unsigned short cacheIndex;
NSHashTable *retainedObjectsTable;
} NCTable;
#define TABLE ((NCTable*)_table)
@ -388,7 +390,9 @@ static void endNCTable(NCTable *t)
NSZoneFree(NSDefaultMallocZone(), (void*)t->cache[i]);
}
NSZoneFree(NSDefaultMallocZone(), t->chunks);
RELEASE(t->retainedObjectsTable);
NSZoneFree(NSDefaultMallocZone(), t);
}
static NCTable *newNCTable(void)
@ -404,6 +408,7 @@ static NCTable *newNCTable(void)
GSIMapInitWithZoneAndCapacity(t->nameless, _zone, 16);
GSIMapInitWithZoneAndCapacity(t->named, _zone, 128);
t->named->extra = YES; // This table retains keys
t->retainedObjectsTable = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:10];
t->_lock = [NSRecursiveLock new];
return t;
@ -722,7 +727,7 @@ static NSNotificationCenter *default_center = nil;
- (void) dealloc
{
[self finalize];
[super dealloc];
}
@ -758,10 +763,18 @@ static NSNotificationCenter *default_center = nil;
* the notification center before releasing these objects.<br />
* </p>
*
* <p>While it is good practice to remove an observer before releasing it,
* currently on MacOS it is possible to remove an observer even after the
* object has been deallocated. This is not documented behavior from Apple
* and could change at any time. In the interests of compatibility, this behavior
* will also be supported here.
* </p>
*
* <p>NB. For MacOS-X compatibility, adding an observer multiple times will
* register it to receive multiple copies of any matching notification, however
* removing an observer will remove <em>all</em> of the multiple registrations.
* </p>
*
*/
- (void) addObserver: (id)observer
selector: (SEL)selector
@ -793,6 +806,11 @@ static NSNotificationCenter *default_center = nil;
o = obsNew(TABLE, selector, observer);
if (object_getClass(observer) == GSNotificationObserverClass)
{
[TABLE->retainedObjectsTable addObject:observer];
}
/*
* Record the Observation in one of the linked lists.
*
@ -1050,15 +1068,15 @@ static NSNotificationCenter *default_center = nil;
GSIMapRemoveKey(NAMED, (GSIMapKey)((id)name));
}
}
unlockNCTable(TABLE);
/* As a special case GSNotificationObserver instances are owned by the
* notification center and are released when they are removed.
*/
if (object_getClass(observer) == GSNotificationObserverClass)
if ([TABLE->retainedObjectsTable containsObject:observer])
{
[TABLE->retainedObjectsTable removeObject:observer];
RELEASE(observer);
}
unlockNCTable(TABLE);
}
/**