Files
cappuccino/Foundation/CPNotificationCenter.j
T
Alexandre Wilhelm 3e29732e46 Fixed: OperationQueue for the CPNotificationCenter is not ignored anymore
Previously, the operationQueue given to the notificationCenter was ignored.

Tests in CPNotificationCenterTest.j
2015-10-08 11:23:07 -07:00

438 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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"
@import "CPOperationQueue.j"
@import "CPOperation.j"
@import "CPSet.j"
@class _CPNotificationRegistry
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 = @{};
_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 = [self _registryForNotificationName:aNotificationName],
observer = [[_CPNotificationObserver alloc] initWithObserver:anObserver selector:aSelector];
[registry addObserver:observer object:anObject];
}
/*!
Adds an entry to the receivers dispatch table with a block, and optional criteria: notification name and sender.
@param aNotificationName the name of the notification the observer wants to watch
@param anObject the object in the notification the observer wants to watch
@param The operation queue to which block should be added. If you pass nil, the block is run synchronously on the posting thread.
@param block the block to be executed when the notification is received.
*/
- (id <CPObject>)addObserverForName:(CPString)aNotificationName object:(id)anObject queue:(CPOperationQueue)queue usingBlock:(Function)block
{
var registry = [self _registryForNotificationName:aNotificationName],
observer = [[_CPNotificationObserver alloc] initWithBlock:block queue:queue];
[registry addObserver:observer object:anObject];
return observer;
}
/*!
@ignore
*/
- (_CPNotificationRegistry)_registryForNotificationName:(CPString)aNotificationName
{
var registry;
if (aNotificationName == nil)
registry = _unnamedRegistry;
else if (!(registry = [_namedRegistries objectForKey:aNotificationName]))
{
registry = [[_CPNotificationRegistry alloc] init];
[_namedRegistries setObject:registry forKey:aNotificationName];
}
return registry;
}
/*!
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]) !== nil)
[[_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]) !== nil)
[[_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;
}
- (id)init
{
self = [super init];
if (self)
{
_objectObservers = @{};
}
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 = [CPMutableSet set];
[_objectObservers setObject:observers forKey:[anObject UID]];
}
// Add this observer.
[observers addObject: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]) !== nil)
{
var observers = [_objectObservers objectForKey:key],
observer = nil,
observersEnumerator = [observers objectEnumerator];
while ((observer = [observersEnumerator nextObject]) !== nil)
if ([observer observer] == anObserver ||
([observer block] && [anObserver respondsToSelector:@selector(block)] && [observer block] == [anObserver block]))
[observers removeObject:observer];
if (![observers count])
removedKeys.push(key);
}
}
else
{
var key = [anObject UID],
observers = [_objectObservers objectForKey:key],
observer = nil,
observersEnumerator = [observers objectEnumerator];
while ((observer = [observersEnumerator nextObject]) !== nil)
if ([observer observer] == anObserver ||
([observer block] && [anObserver respondsToSelector:@selector(block)] && [observer block] == [anObserver block]))
[observers removeObject:observer];
if (![observers count])
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 set (this avoids removed observers from receiving notifications).
var object = [aNotification object],
currentObservers = nil;
if (object != nil && (currentObservers = [_objectObservers objectForKey:[object UID]]))
{
var observers = [currentObservers copy],
observer = nil,
observersEnumerator = [observers objectEnumerator];
while ((observer = [observersEnumerator nextObject]) !== nil)
{
// CPSet containsObject is N(1) so this is a fast check.
if ([currentObservers containsObject:observer])
[observer postNotification:aNotification];
}
}
// Now do the same for the nil object observers...
currentObservers = [_objectObservers objectForKey:[[CPNull null] UID]];
if (!currentObservers)
return;
var observers = [currentObservers copy],
observersEnumerator = [observers objectEnumerator];
while ((observer = [observersEnumerator nextObject]) !== nil)
{
// CPSet containsObject is N(1) so this is a fast check.
if ([currentObservers containsObject:observer])
[observer postNotification:aNotification];
}
}
- (unsigned)count
{
return [_objectObservers count];
}
@end
/* @ignore */
@implementation _CPNotificationObserver : CPObject
{
CPOperationQueue _operationQueue;
id _observer;
Function _block;
SEL _selector;
}
- (id)initWithObserver:(id)anObserver selector:(SEL)aSelector
{
if (self)
{
_observer = anObserver;
_selector = aSelector;
}
return self;
}
- (id)initWithBlock:(Function)aBlock queue:(CPOperationQueue)aQueue
{
if (self)
{
_block = aBlock;
_operationQueue = aQueue;
}
return self;
}
- (id)observer
{
return _observer;
}
- (id)block
{
return _block;
}
- (void)postNotification:(CPNotification)aNotification
{
if (_block)
{
if (!_operationQueue)
_block(aNotification);
else
[_operationQueue addOperation:[[_CPNotificationObserverOperation alloc] initWithBlock:_block notification:aNotification]];
return;
}
[_observer performSelector:_selector withObject:aNotification];
}
@end
/* @ignore */
@implementation _CPNotificationObserverOperation : CPOperation
{
CPNotification _notification;
Function _block;
}
/* @ignore */
- (id)initWithBlock:(Function)aBlock notification:(CPNotification)aNotification
{
self = [super init];
if (self)
{
_block = aBlock;
_notification = aNotification;
}
return self;
}
/* @ignore */
- (void)main
{
_block(_notification);
}
/* @ignore */
- (BOOL)isReady
{
return YES;
}
@end