mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 13:07:02 +00:00
If one observer of a notification centre notification removed another observer, that other observer would still be notified even that it was no longer observing. Now the code agrees with its comments.
357 lines
11 KiB
Plaintext
357 lines
11 KiB
Plaintext
/*
|
|
* CPNotificationCenter.j
|
|
* Foundation
|
|
*
|
|
* Created by Francisco Tolmasky.
|
|
* Copyright 2008, 280 North, Inc.
|
|
*
|
|
* 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.1 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
@import "CPArray.j"
|
|
@import "CPDictionary.j"
|
|
@import "CPException.j"
|
|
@import "CPNotification.j"
|
|
@import "CPNull.j"
|
|
|
|
var CPNotificationDefaultCenter = nil;
|
|
|
|
/*!
|
|
@class CPNotificationCenter
|
|
@ingroup foundation
|
|
@brief Sends messages (CPNotification) between objects.
|
|
|
|
Cappuccino provides a framework for sending messages between objects within
|
|
a process called notifications. Objects register with an
|
|
CPNotificationCenter to be informed whenever other objects post
|
|
CPNotifications to it matching certain criteria. The notification center
|
|
processes notifications synchronously -- that is, control is only returned
|
|
to the notification poster once every recipient of the notification has
|
|
received it and processed it.
|
|
*/
|
|
@implementation CPNotificationCenter : CPObject
|
|
{
|
|
CPMutableDictionary _namedRegistries;
|
|
_CPNotificationRegistry _unnamedRegistry;
|
|
}
|
|
|
|
/*!
|
|
Returns the application's notification center
|
|
*/
|
|
+ (CPNotificationCenter)defaultCenter
|
|
{
|
|
if (!CPNotificationDefaultCenter)
|
|
CPNotificationDefaultCenter = [[CPNotificationCenter alloc] init];
|
|
|
|
return CPNotificationDefaultCenter;
|
|
}
|
|
|
|
- (id)init
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_namedRegistries = [CPDictionary dictionary];
|
|
_unnamedRegistry = [[_CPNotificationRegistry alloc] init];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
/*!
|
|
Adds an object as an observer. The observer will receive notifications with the specified name
|
|
and/or containing the specified object (depending on if they are \c nil.
|
|
@param anObserver the observing object
|
|
@param aSelector the message sent to the observer when a notification occurs
|
|
@param aNotificationName the name of the notification the observer wants to watch
|
|
@param anObject the object in the notification the observer wants to watch
|
|
*/
|
|
- (void)addObserver:(id)anObserver selector:(SEL)aSelector name:(CPString)aNotificationName object:(id)anObject
|
|
{
|
|
var registry,
|
|
observer = [[_CPNotificationObserver alloc] initWithObserver:anObserver selector:aSelector];
|
|
|
|
if (aNotificationName == nil)
|
|
registry = _unnamedRegistry;
|
|
|
|
else if (!(registry = [_namedRegistries objectForKey:aNotificationName]))
|
|
{
|
|
registry = [[_CPNotificationRegistry alloc] init];
|
|
[_namedRegistries setObject:registry forKey:aNotificationName];
|
|
}
|
|
|
|
[registry addObserver:observer object:anObject];
|
|
}
|
|
|
|
/*!
|
|
Unregisters the specified observer from all notifications.
|
|
@param anObserver the observer to unregister
|
|
*/
|
|
- (void)removeObserver:(id)anObserver
|
|
{
|
|
var name = nil,
|
|
names = [_namedRegistries keyEnumerator];
|
|
|
|
while (name = [names nextObject])
|
|
[[_namedRegistries objectForKey:name] removeObserver:anObserver object:nil];
|
|
|
|
[_unnamedRegistry removeObserver:anObserver object:nil];
|
|
}
|
|
|
|
/*!
|
|
Unregisters the specified observer from notifications matching the specified name and/or object.
|
|
@param anObserver the observer to remove
|
|
@param aNotificationName the name of notifications to no longer watch
|
|
@param anObject notifications containing this object will no longer be watched
|
|
*/
|
|
- (void)removeObserver:(id)anObserver name:(CPString)aNotificationName object:(id)anObject
|
|
{
|
|
if (aNotificationName == nil)
|
|
{
|
|
var name = nil,
|
|
names = [_namedRegistries keyEnumerator];
|
|
|
|
while (name = [names nextObject])
|
|
[[_namedRegistries objectForKey:name] removeObserver:anObserver object:anObject];
|
|
|
|
[_unnamedRegistry removeObserver:anObserver object:anObject];
|
|
}
|
|
else
|
|
[[_namedRegistries objectForKey:aNotificationName] removeObserver:anObserver object:anObject];
|
|
}
|
|
|
|
/*!
|
|
Posts a notification to all observers that match the specified notification's name and object.
|
|
@param aNotification the notification being posted
|
|
@throws CPInvalidArgumentException if aNotification is nil
|
|
*/
|
|
- (void)postNotification:(CPNotification)aNotification
|
|
{
|
|
if (!aNotification)
|
|
[CPException raise:CPInvalidArgumentException reason:"postNotification: does not except 'nil' notifications"];
|
|
|
|
_CPNotificationCenterPostNotification(self, aNotification);
|
|
}
|
|
|
|
/*!
|
|
Posts a new notification with the specified name, object, and dictionary.
|
|
@param aNotificationName the name of the notification name
|
|
@param anObject the associated object
|
|
@param aUserInfo the associated dictionary
|
|
*/
|
|
- (void)postNotificationName:(CPString)aNotificationName object:(id)anObject userInfo:(CPDictionary)aUserInfo
|
|
{
|
|
_CPNotificationCenterPostNotification(self, [[CPNotification alloc] initWithName:aNotificationName object:anObject userInfo:aUserInfo]);
|
|
}
|
|
|
|
/*!
|
|
Posts a new notification with the specified name and object.
|
|
@param aNotificationName the name of the notification
|
|
@param anObject the associated object
|
|
*/
|
|
- (void)postNotificationName:(CPString)aNotificationName object:(id)anObject
|
|
{
|
|
_CPNotificationCenterPostNotification(self, [[CPNotification alloc] initWithName:aNotificationName object:anObject userInfo:nil]);
|
|
}
|
|
|
|
@end
|
|
|
|
var _CPNotificationCenterPostNotification = function(/* CPNotificationCenter */ self, /* CPNotification */ aNotification)
|
|
{
|
|
[self._unnamedRegistry postNotification:aNotification];
|
|
[[self._namedRegistries objectForKey:[aNotification name]] postNotification:aNotification];
|
|
}
|
|
|
|
/*
|
|
Mapping of Notification Name to listening object/selector.
|
|
@ignore
|
|
*/
|
|
@implementation _CPNotificationRegistry : CPObject
|
|
{
|
|
CPDictionary _objectObservers;
|
|
BOOL _observerRemovalCount;
|
|
}
|
|
|
|
- (id)init
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_observerRemovalCount = 0;
|
|
_objectObservers = [CPDictionary dictionary];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)addObserver:(_CPNotificationObserver)anObserver object:(id)anObject
|
|
{
|
|
// If there's no object, then we're listening to this
|
|
// notification regardless of whom sends it.
|
|
if (!anObject)
|
|
anObject = [CPNull null];
|
|
|
|
// Grab all the listeners for this notification/object pair
|
|
var observers = [_objectObservers objectForKey:[anObject UID]];
|
|
|
|
if (!observers)
|
|
{
|
|
observers = [];
|
|
[_objectObservers setObject:observers forKey:[anObject UID]];
|
|
}
|
|
|
|
// Add this observer.
|
|
observers.push(anObserver);
|
|
}
|
|
|
|
- (void)removeObserver:(id)anObserver object:(id)anObject
|
|
{
|
|
var removedKeys = [];
|
|
|
|
// This means we're getting rid of EVERY instance of this observer.
|
|
if (anObject == nil)
|
|
{
|
|
var key = nil,
|
|
keys = [_objectObservers keyEnumerator];
|
|
|
|
// Iterate through every set of observers
|
|
while (key = [keys nextObject])
|
|
{
|
|
var observers = [_objectObservers objectForKey:key],
|
|
count = observers ? observers.length : 0;
|
|
|
|
while (count--)
|
|
if ([observers[count] observer] == anObserver)
|
|
{
|
|
++_observerRemovalCount;
|
|
observers.splice(count, 1);
|
|
}
|
|
|
|
if (!observers || observers.length == 0)
|
|
removedKeys.push(key);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var key = [anObject UID],
|
|
observers = [_objectObservers objectForKey:key],
|
|
count = observers ? observers.length : 0;
|
|
|
|
while (count--)
|
|
if ([observers[count] observer] == anObserver)
|
|
{
|
|
++_observerRemovalCount;
|
|
observers.splice(count, 1)
|
|
}
|
|
|
|
if (!observers || observers.length == 0)
|
|
removedKeys.push(key);
|
|
}
|
|
|
|
var count = removedKeys.length;
|
|
|
|
while (count--)
|
|
[_objectObservers removeObjectForKey:removedKeys[count]];
|
|
}
|
|
|
|
- (void)postNotification:(CPNotification)aNotification
|
|
{
|
|
// We don't want to erroneously send notifications to observers that get removed
|
|
// during the posting of this notification, nor observers that get added. The
|
|
// best way to do this is to make a copy of the current observers (this avoids
|
|
// new observers from being notified) and double checking every observer against
|
|
// the current array (this avoids removed observers from receiving notifications).
|
|
// However, this is a very expensive operation (O(N) => O(N^2)), so to avoid it,
|
|
// we keep track of whether observers are added or removed, and only do our
|
|
// rigorous testing in those cases.
|
|
var observerRemovalCount = _observerRemovalCount,
|
|
object = [aNotification object],
|
|
observers = nil;
|
|
|
|
if (object != nil && (currentObservers = [_objectObservers objectForKey:[object UID]]))
|
|
{
|
|
var observers = [currentObservers copy],
|
|
count = observers.length;
|
|
|
|
while (count--)
|
|
{
|
|
var observer = observers[count];
|
|
|
|
// if there wasn't removal of an observer during this posting, or there
|
|
// was but we are still in the observer list...
|
|
if ((observerRemovalCount === _observerRemovalCount) || [currentObservers indexOfObjectIdenticalTo:observer] !== CPNotFound)
|
|
[observer postNotification:aNotification];
|
|
}
|
|
}
|
|
|
|
// Now do the same for the nil object observers...
|
|
currentObservers = [_objectObservers objectForKey:[[CPNull null] UID]];
|
|
|
|
if (!currentObservers)
|
|
return;
|
|
|
|
var observerRemovalCount = _observerRemovalCount,
|
|
observers = [currentObservers copy],
|
|
count = observers.length;
|
|
|
|
while (count--)
|
|
{
|
|
var observer = observers[count];
|
|
|
|
// if there wasn't removal of an observer during this posting, or there
|
|
// was but we are still in the observer list...
|
|
if ((observerRemovalCount === _observerRemovalCount) || [currentObservers indexOfObjectIdenticalTo:observer] !== CPNotFound)
|
|
[observer postNotification:aNotification];
|
|
}
|
|
}
|
|
|
|
- (unsigned)count
|
|
{
|
|
return [_objectObservers count];
|
|
}
|
|
|
|
@end
|
|
|
|
/* @ignore */
|
|
@implementation _CPNotificationObserver : CPObject
|
|
{
|
|
id _observer;
|
|
SEL _selector;
|
|
}
|
|
|
|
- (id)initWithObserver:(id)anObserver selector:(SEL)aSelector
|
|
{
|
|
if (self)
|
|
{
|
|
_observer = anObserver;
|
|
_selector = aSelector;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (id)observer
|
|
{
|
|
return _observer;
|
|
}
|
|
|
|
- (void)postNotification:(CPNotification)aNotification
|
|
{
|
|
[_observer performSelector:_selector withObject:aNotification];
|
|
}
|
|
|
|
@end
|