This is NotificationDispatcher.m in view mode; [Download] [Up]
/* Implementation of object for broadcasting Notification objects Copyright (C) 1996 Free Software Foundation, Inc. Written by: Andrew Kachites McCallum <mccallum@gnu.ai.mit.edu> Created: March 1996 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 Library 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 Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* The implementation for NotificationDispatcher. */ /* NeXT says you can only have one NotificationCenter per task; I don't think GNU needs this restriction with its corresponding NotificationDistributor class. */ #include <gnustep/base/NotificationDispatcher.h> #include <gnustep/base/Notification.h> #include <gnustep/base/LinkedListNode.h> #include <gnustep/base/Array.h> #include <gnustep/base/Invocation.h> #include <Foundation/NSException.h> /* NotificationRequest class - One of these objects is created for each -addObserver... request. It holds the requested invocation, name and object. Each object is placed (1) in one LinkedList, as keyed by the NAME/OBJECT parameters (accessible through one of the ivars: anonymous_nr_list, object_2_nr_list, name_2_nr_list), and (2) in the Array, as keyed by the OBSERVER (as accessible through the ivar observer_2_nr_array. To post a notification in satisfaction of this request, send -postNotification:. */ @interface NotificationRequest : LinkedListNode { int _retain_count; id _name; id _object; } - initWithName: n object: o; - (id <String>) notificationName; - notificationObject; - (void) postNotification: n; @end @implementation NotificationRequest - initWithName: n object: o { [super init]; _retain_count = 0; _name = [n retain]; _object = o; /* Note that OBJECT is not retained. See the comment for -addObserver... in NotificationDispatcher.h. */ return self; } /* Implement these retain/release methods here for efficiency, since NotificationRequest's get retained and released by all their holders. Doing this is a judgement call; I'm choosing speed over space. */ - retain { _retain_count++; return self; } - (oneway void) release { if (!_retain_count--) [self dealloc]; } - (unsigned) retainCount { return _retain_count; } - (void) dealloc { [_name release]; [super dealloc]; } - (id <String>) notificationName { return _name; } - notificationObject { return _object; } - (void) postNotification: n { [self subclassResponsibility: _cmd]; } @end @interface NotificationInvocation : NotificationRequest { id _invocation; } - initWithInvocation: i name: n object: o; @end @implementation NotificationInvocation - initWithInvocation: i name: n object: o { [super initWithName: n object: o]; _invocation = [i retain]; return self; } - (void) dealloc { [_invocation release]; [super dealloc]; } - (void) postNotification: n { [_invocation invokeWithObject: n]; } @end @interface NotificationPerformer : NotificationRequest { id _target; SEL _selector; } - initWithTarget: t selector: (SEL)s name: n object: o; @end @implementation NotificationPerformer - initWithTarget: t selector: (SEL)s name: n object: o { [super initWithName: n object: o]; /* Note that TARGET is not retained. See the comment for -addObserver... in NotificationDispatcher.h. */ _target = t; _selector = s; return self; } - (void) postNotification: n { [_target perform: _selector withObject: n]; } @end @implementation NotificationDispatcher /* The default instance, most often the only one created. It is accessed by the class methods at the end of this file. There is no need to mutex locking of this variable. */ static NotificationDispatcher *default_notification_dispatcher = nil; + (void) initialize { if (self == [NotificationDispatcher class]) default_notification_dispatcher = [self new]; } /* Initializing. */ - init { [super init]; _anonymous_nr_list = [LinkedList new]; /* Use NSNonOwnedPointerOrNullMapKeyCallBacks so we won't retain the object. We will, however, retain the values, which are LinkedList's. */ _object_2_nr_list = NSCreateMapTable (NSNonOwnedPointerOrNullMapKeyCallBacks, NSObjectMapValueCallBacks, 0); /* Use NSObjectMapKeyCallBacks so we retain the NAME. We also retain the values, which are LinkedList's. */ _name_2_nr_list = NSCreateMapTable (NSNonOwnedPointerOrNullMapKeyCallBacks, NSObjectMapValueCallBacks, 0); /* Use NSNonOwnedPointerOrNullMapKeyCallBacks so we won't retain the observer. We will, however, retain the values, which are Array's. */ _observer_2_nr_array = NSCreateMapTable (NSNonOwnedPointerOrNullMapKeyCallBacks, NSObjectMapValueCallBacks, 0); _lock = [NSRecursiveLock new]; return self; } - (void) dealloc { [_anonymous_nr_list release]; NSFreeMapTable( _object_2_nr_list); NSFreeMapTable (_name_2_nr_list); NSFreeMapTable (_observer_2_nr_array); [_lock release]; [super dealloc]; } /* Adding new observers. */ /* This is the (private) designated method for adding observers. If we came from -addInvocation... then OBSERVER is actually an Invocation. */ - (void) _addObserver: observer notificationRequest: nr name: (id <String>)name object: object { /* If observer is nil, there is nothing to do; return. */ if (!observer) return; [_lock lock]; /* Record the notification request in an array keyed by OBSERVER. */ { /* Find the array of all the requests by OBSERVER. */ Array *nr_array = NSMapGet (_observer_2_nr_array, observer); if (!nr_array) { nr_array = [Array new]; /* nr_array is retained; observer is not. */ NSMapInsert (_observer_2_nr_array, observer, nr_array); /* Now that nr_array is retained by the map table, release it; this way the array will be completely released when the map table is done with it. */ [nr_array release]; } [nr_array appendObject: nr]; } /* Record the NotificationRequest in one of three MapTable->LinkedLists. */ /* Record the request in one, and only one, LinkedList. The LinkedList is stored in a hash table accessed by a key. Which key is used depends on what combination of NAME and OBJECT are non-nil. */ if (!name) { if (!object) { /* This NotificationRequest will get posted notifications for all NAME and OBJECT combinations. */ [_anonymous_nr_list appendObject: nr]; } else { LinkedList *nr_list = NSMapGet (_object_2_nr_list, object); if (!nr_list) { nr_list = [LinkedList new]; /* nr_list is retained; object is not retained. */ NSMapInsert (_object_2_nr_list, object, nr_list); /* Now that nr_list is retained by the map table, release it; this way the list will be completely released when the map table is done with it. */ [nr_list release]; } [nr_list appendObject: nr]; } } else { LinkedList *nr_list = NSMapGet (_name_2_nr_list, name); if (!nr_list) { nr_list = [LinkedList new]; /* nr_list is retained; object is not retained. */ NSMapInsert (_name_2_nr_list, name, nr_list); /* Now that nr_list is retained by the map table, release it; this way the list will be completely released when the map table is done with it. */ [nr_list release]; } [nr_list appendObject: nr]; } [_lock unlock]; } - (void) addInvocation: (id <Invoking>)invocation name: (id <String>)name object: object { id nr; /* Create the NotificationRequest object that will hold this observation request. This will retain INVOCATION and NAME. */ nr = [[NotificationInvocation alloc] initWithInvocation: invocation name: name object: object]; /* Record it in all the right places. */ [self _addObserver: invocation notificationRequest: nr name: name object: object]; /* Since nr was retained when it was added to the Array and LinkedList above, we can release it now. */ [nr release]; } /* For those that want to specify a selector instead of an invocation as a way to contact the observer. */ - (void) addObserver: observer selector: (SEL)sel name: (id <String>)name object: object { id nr; /* Create the NotificationRequest object that will hold this observation request. This will retain INVOCATION and NAME. */ nr = [[NotificationPerformer alloc] initWithTarget: observer selector: sel name: name object: object]; /* Record it in all the right places. */ [self _addObserver: observer notificationRequest: nr name: name object: object]; /* Since nr was retained when it was added to the Array and LinkedList above, we can release it now. */ [nr release]; } /* Removing objects. */ /* A private method. Remove the NR object from its one LinkedList; if this is the last element of that LinkedList, and the LinkedList is map-accessible, also release the LinkedList. */ - (void) _removeFromLinkedListNotificationRequest: nr { id nr_list = [nr linkedList]; /* See if, instead of removing the NR from its LinkedList, we can actually release the entire list. */ if ([nr_list count] == 1 && nr_list != _anonymous_nr_list) { id nr_name; id nr_object; LinkedList *mapped_nr_list; assert ([nr_list firstObject] == nr); if ((nr_name = [nr notificationName])) { mapped_nr_list = NSMapGet (_name_2_nr_list, nr_name); assert (mapped_nr_list == nr_list); NSMapRemove (_name_2_nr_list, nr_name); } else { nr_object = [nr notificationObject]; assert (nr_object); mapped_nr_list = NSMapGet (_object_2_nr_list, nr_object); assert (mapped_nr_list == nr_list); NSMapRemove (_object_2_nr_list, nr_object); } } else [nr_list removeObject: nr]; } /* Removing notification requests. */ /* Remove all notification requests that would be sent to INVOCATION. */ - (void) removeInvocation: invocation { [self removeObserver: invocation]; } /* Remove the notification requests matching NAME and OBJECT that would be sent to INVOCATION. As with adding an observation request, nil NAME or OBJECT act as wildcards. */ - (void) removeInvocation: invocation name: (id <String>)name object: object { [self removeObserver: invocation name: name object: object]; } /* Remove all records pertaining to OBSERVER. For instance, this should be called before the OBSERVER is -dealloc'ed. */ - (void) removeObserver: observer { Array *observer_nr_array; NotificationRequest *nr; /* If OBSERVER is nil, do nothing; just return. NOTE: This *does not* remove all requests with a nil OBSERVER; it would be too easy to unintentionally remove other's requests that way. If you need to remove a request with a nil OBSERVER, use -removeObserver:name:object: */ if (!observer) return; [_lock lock]; /* Get the array of NotificationRequest's associated with OBSERVER. */ observer_nr_array = NSMapGet (_observer_2_nr_array, observer); if (!observer_nr_array) /* OBSERVER was never registered for any notification requests with us. Nothing to do. */ return; /* Remove each of these from it's LinkedList. */ FOR_ARRAY (observer_nr_array, nr) { [self _removeFromLinkedListNotificationRequest: nr]; } END_FOR_ARRAY (observer_nr_array); /* Remove from the MapTable the list of NotificationRequest's associated with OBSERVER. This also releases the observer_nr_array, and its contents. */ NSMapRemove (_observer_2_nr_array, observer); [_lock unlock]; } /* Remove the notification requests for the given parameters. As with adding an observation request, nil NAME or OBJECT act as wildcards. */ - (void) removeObserver: observer name: (id <String>)name object: object { Array *observer_nr_array; /* If both NAME and OBJECT are nil, this call is the same as -removeObserver:, so just call it. */ if (!name && !object) [self removeObserver: observer]; /* We are now guaranteed that at least one of NAME and OBJECT is non-nil. */ [_lock lock]; /* Get the list of NotificationRequest's associated with OBSERVER. */ observer_nr_array = NSMapGet (_observer_2_nr_array, observer); if (!observer_nr_array) /* OBSERVER was never registered for any notification requests with us. Nothing to do. */ return; /* Find those NotificationRequest's from the array that match NAME and OBJECT, and remove them from the array and their linked list. */ /* xxx If we thought the LinkedList from the map table keyed on NAME would be shorter, we could use that instead. */ { NotificationRequest *nr; int count = [observer_nr_array count]; int i; for (i = count-1; i >= 0; i--) { nr = [observer_nr_array objectAtIndex: i]; if ((!name || [name isEqual: [nr notificationName]]) && (!object || [object isEqual: [nr notificationObject]])) { /* We can remove from the array, even though we are "enumerating" over it, because we are enumerating from back-to-front, and the indices of yet-to-come objects don't change when high-indexed objects are removed. */ [observer_nr_array removeObjectAtIndex: i]; [self _removeFromLinkedListNotificationRequest: nr]; } } /* xxx If there are some LinkedList's that are empty, I should remove them from the map table's. */ } [_lock unlock]; } /* Post NOTIFICATION to all the observers that match its NAME and OBJECT. */ - (void) postNotification: notification { /* This cast avoids complaints about different types for the -name method. */ id notification_name = [(Notification*)notification name]; id notification_object = [notification object]; id nr; LinkedList *nr_list; /* Make sure the notification has a name. */ if (!notification_name) [NSException raise: NSInvalidArgumentException format: @"Tried to post a notification with no name."]; [_lock lock]; /* Post the notification to all the observers that specified neither NAME or OBJECT. */ if ([_anonymous_nr_list count]) { FOR_COLLECTION (_anonymous_nr_list, nr) { [nr postNotification: notification]; } END_FOR_COLLECTION (_anonymous_nr_list); } /* Post the notification to all the observers that specified OBJECT, but didn't specify NAME. */ if (notification_object) { nr_list = NSMapGet (_object_2_nr_list, notification_object); if (nr_list) { FOR_COLLECTION (nr_list, nr) { [nr postNotification: notification]; } END_FOR_COLLECTION (nr_list); } } /* Post the notification to all the observers of NAME; (and if the observer's OBJECT is non-nil, don't send unless the observer's OBJECT matches the notification's OBJECT). */ nr_list = NSMapGet (_name_2_nr_list, notification_name); if (nr_list) { FOR_COLLECTION (nr_list, nr) { id nr_object = [nr notificationObject]; if (!nr_object || nr_object == notification_object) [nr postNotification: notification]; } END_FOR_COLLECTION (nr_list); } [_lock unlock]; } - (void) postNotificationName: (id <String>)name object: object { [self postNotification: [Notification notificationWithName: name object: object]]; } - (void) postNotificationName: (id <String>)name object: object userInfo: info { [self postNotification: [Notification notificationWithName: name object: object userInfo: info]]; } /* Class methods. */ + defaultInstance { return default_notification_dispatcher; } + (void) addInvocation: (id <Invoking>)invocation name: (id <String>)name object: object { [default_notification_dispatcher addInvocation: invocation name: name object: object]; } + (void) addObserver: observer selector: (SEL)sel name: (id <String>)name object: object { [default_notification_dispatcher addObserver: observer selector: sel name: name object: object]; } + (void) removeInvocation: invocation { [default_notification_dispatcher removeInvocation: invocation]; } + (void) removeInvocation: invocation name: (id <String>)name object: object { [default_notification_dispatcher removeInvocation: invocation name: name object: object]; } + (void) removeObserver: observer { [default_notification_dispatcher removeObserver: observer]; } + (void) removeObserver: observer name: (id <String>)name object: object { [default_notification_dispatcher removeObserver: observer name: name object: object]; } + (void) postNotification: notification { [default_notification_dispatcher postNotification: notification]; } + (void) postNotificationName: (id <String>)name object: object { [default_notification_dispatcher postNotificationName: name object: object]; } + (void) postNotificationName: (id <String>)name object: object userInfo: info { [default_notification_dispatcher postNotificationName: name object: object userInfo: info]; } @end @implementation NotificationDispatcher (OpenStepCompat) /* For OpenStep compatibility. */ + defaultCenter { return default_notification_dispatcher; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.