/* * 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]) !== 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 = [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 = [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) [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) [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 { 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