Files
cappuccino/Foundation/CPNotificationCenter.j
T

358 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 occurrs
@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 && (observers = [[_objectObservers objectForKey:[object UID]] copy]))
{
var currentObservers = observers,
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...
observers = [[_objectObservers objectForKey:[[CPNull null] UID]] copy];
if (!observers)
return;
var observerRemovalCount = _observerRemovalCount,
count = observers.length,
currentObservers = observers;
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