API  1.0.0
CPNotificationCenter.j
Go to the documentation of this file.
1 /*
2  * CPNotificationCenter.j
3  * Foundation
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
26 
39 @implementation CPNotificationCenter : CPObject
40 {
41  CPMutableDictionary _namedRegistries;
42  _CPNotificationRegistry _unnamedRegistry;
43 }
44 
48 + (CPNotificationCenter)defaultCenter
49 {
52 
54 }
55 
56 - (id)init
57 {
58  self = [super init];
59 
60  if (self)
61  {
62  _namedRegistries = @{};
63  _unnamedRegistry = [[_CPNotificationRegistry alloc] init];
64  }
65 
66  return self;
67 }
68 
77 - (void)addObserver:(id)anObserver selector:(SEL)aSelector name:(CPString)aNotificationName object:(id)anObject
78 {
79  var registry = [self _registryForNotificationName:aNotificationName],
80  observer = [[_CPNotificationObserver alloc] initWithObserver:anObserver selector:aSelector];
81 
82  [registry addObserver:observer object:anObject];
83 }
84 
92 - (id <CPObject>)addObserverForName:(CPString)aNotificationName object:(id)anObject queue:(CPOperationQueue)queue usingBlock:(Function)block
93 {
94  var registry = [self _registryForNotificationName:aNotificationName],
95  observer = [[_CPNotificationObserver alloc] initWithBlock:block queue:queue];
96 
97  [registry addObserver:observer object:anObject];
98 
99  return observer;
100 }
101 
105 - (_CPNotificationRegistry)_registryForNotificationName:(CPString)aNotificationName
106 {
107  var registry;
108 
109  if (aNotificationName == nil)
110  registry = _unnamedRegistry;
111  else if (!(registry = [_namedRegistries objectForKey:aNotificationName]))
112  {
113  registry = [[_CPNotificationRegistry alloc] init];
114  [_namedRegistries setObject:registry forKey:aNotificationName];
115  }
116 
117  return registry;
118 }
119 
124 - (void)removeObserver:(id)anObserver
125 {
126  var name = nil,
127  names = [_namedRegistries keyEnumerator];
128 
129  while ((name = [names nextObject]) !== nil)
130  [[_namedRegistries objectForKey:name] removeObserver:anObserver object:nil];
131 
132  [_unnamedRegistry removeObserver:anObserver object:nil];
133 }
134 
141 - (void)removeObserver:(id)anObserver name:(CPString)aNotificationName object:(id)anObject
142 {
143  if (aNotificationName == nil)
144  {
145  var name = nil,
146  names = [_namedRegistries keyEnumerator];
147 
148  while ((name = [names nextObject]) !== nil)
149  [[_namedRegistries objectForKey:name] removeObserver:anObserver object:anObject];
150 
151  [_unnamedRegistry removeObserver:anObserver object:anObject];
152  }
153  else
154  [[_namedRegistries objectForKey:aNotificationName] removeObserver:anObserver object:anObject];
155 }
156 
162 - (void)postNotification:(CPNotification)aNotification
163 {
164  if (!aNotification)
165  [CPException raise:CPInvalidArgumentException reason:"postNotification: does not except 'nil' notifications"];
166 
167  _CPNotificationCenterPostNotification(self, aNotification);
168 }
169 
176 - (void)postNotificationName:(CPString)aNotificationName object:(id)anObject userInfo:(CPDictionary)aUserInfo
177 {
178  _CPNotificationCenterPostNotification(self, [[CPNotification alloc] initWithName:aNotificationName object:anObject userInfo:aUserInfo]);
179 }
180 
186 - (void)postNotificationName:(CPString)aNotificationName object:(id)anObject
187 {
188  _CPNotificationCenterPostNotification(self, [[CPNotification alloc] initWithName:aNotificationName object:anObject userInfo:nil]);
189 }
190 
191 @end
192 
193 var _CPNotificationCenterPostNotification = function(/* CPNotificationCenter */ self, /* CPNotification */ aNotification)
194 {
195  [self._unnamedRegistry postNotification:aNotification];
196  [[self._namedRegistries objectForKey:[aNotification name]] postNotification:aNotification];
197 };
198 
199 /*
200  Mapping of Notification Name to listening object/selector.
201  @ignore
202  */
203 @implementation _CPNotificationRegistry : CPObject
204 {
205  CPDictionary _objectObservers;
206 }
207 
208 - (id)init
209 {
210  self = [super init];
211 
212  if (self)
213  {
214  _objectObservers = @{};
215  }
216 
217  return self;
218 }
219 
220 - (void)addObserver:(_CPNotificationObserver)anObserver object:(id)anObject
221 {
222  // If there's no object, then we're listening to this
223  // notification regardless of whom sends it.
224  if (!anObject)
225  anObject = [CPNull null];
226 
227  // Grab all the listeners for this notification/object pair
228  var observers = [_objectObservers objectForKey:[anObject UID]];
229 
230  if (!observers)
231  {
232  observers = [CPMutableSet set];
233  [_objectObservers setObject:observers forKey:[anObject UID]];
234  }
235 
236  // Add this observer.
237  [observers addObject:anObserver];
238 }
239 
240 - (void)removeObserver:(id)anObserver object:(id)anObject
241 {
242  var removedKeys = [];
243 
244  // This means we're getting rid of EVERY instance of this observer.
245  if (anObject == nil)
246  {
247  var key = nil,
248  keys = [_objectObservers keyEnumerator];
249 
250  // Iterate through every set of observers
251  while ((key = [keys nextObject]) !== nil)
252  {
253  var observers = [_objectObservers objectForKey:key],
254  observer = nil,
255  observersEnumerator = [observers objectEnumerator];
256 
257  while ((observer = [observersEnumerator nextObject]) !== nil)
258  if ([observer observer] == anObserver ||
259  ([observer block] && [anObserver respondsToSelector:@selector(block)] && [observer block] == [anObserver block]))
260  [observers removeObject:observer];
261 
262  if (![observers count])
263  removedKeys.push(key);
264  }
265  }
266  else
267  {
268  var key = [anObject UID],
269  observers = [_objectObservers objectForKey:key],
270  observer = nil,
271  observersEnumerator = [observers objectEnumerator];
272 
273  while ((observer = [observersEnumerator nextObject]) !== nil)
274  if ([observer observer] == anObserver ||
275  ([observer block] && [anObserver respondsToSelector:@selector(block)] && [observer block] == [anObserver block]))
276  [observers removeObject:observer];
277 
278  if (![observers count])
279  removedKeys.push(key);
280  }
281 
282  var count = removedKeys.length;
283 
284  while (count--)
285  [_objectObservers removeObjectForKey:removedKeys[count]];
286 }
287 
288 - (void)postNotification:(CPNotification)aNotification
289 {
290  // We don't want to erroneously send notifications to observers that get removed
291  // during the posting of this notification, nor observers that get added. The
292  // best way to do this is to make a copy of the current observers (this avoids
293  // new observers from being notified) and double checking every observer against
294  // the current set (this avoids removed observers from receiving notifications).
295  var object = [aNotification object],
296  currentObservers = nil;
297 
298  if (object != nil && (currentObservers = [_objectObservers objectForKey:[object UID]]))
299  {
300  var observers = [currentObservers copy],
301  observer = nil,
302  observersEnumerator = [observers objectEnumerator];
303 
304  while ((observer = [observersEnumerator nextObject]) !== nil)
305  {
306  // CPSet containsObject is N(1) so this is a fast check.
307  if ([currentObservers containsObject:observer])
308  [observer postNotification:aNotification];
309  }
310  }
311 
312  // Now do the same for the nil object observers...
313  currentObservers = [_objectObservers objectForKey:[[CPNull null] UID]];
314 
315  if (!currentObservers)
316  return;
317 
318  var observers = [currentObservers copy],
319  observersEnumerator = [observers objectEnumerator];
320 
321  while ((observer = [observersEnumerator nextObject]) !== nil)
322  {
323  // CPSet containsObject is N(1) so this is a fast check.
324  if ([currentObservers containsObject:observer])
325  [observer postNotification:aNotification];
326  }
327 }
328 
329 - (unsigned)count
330 {
331  return [_objectObservers count];
332 }
333 
334 @end
335 
336 /* @ignore */
337 @implementation _CPNotificationObserver : CPObject
338 {
339  CPOperationQueue _operationQueue;
340  id _observer;
341  Function _block;
342  SEL _selector;
343 }
344 
345 - (id)initWithObserver:(id)anObserver selector:(SEL)aSelector
346 {
347  if (self)
348  {
349  _observer = anObserver;
350  _selector = aSelector;
351  }
352 
353  return self;
354 }
355 
356 - (id)initWithBlock:(Function)aBlock queue:(CPOperationQueue)aQueue
357 {
358  if (self)
359  {
360  _block = aBlock;
361  _operationQueue = aQueue;
362  }
363 
364  return self;
365 }
366 
367 - (id)observer
368 {
369  return _observer;
370 }
371 
372 - (id)block
373 {
374  return _block;
375 }
376 
377 - (void)postNotification:(CPNotification)aNotification
378 {
379  if (_block)
380  {
381  if (!_operationQueue)
382  _block(aNotification);
383  else
384  [_operationQueue addOperation:[[_CPNotificationObserverOperation alloc] initWithBlock:_block notification:aNotification]];
385 
386  return;
387  }
388 
389  [_observer performSelector:_selector withObject:aNotification];
390 }
391 
392 @end
393 
394 /* @ignore */
395 @implementation _CPNotificationObserverOperation : CPOperation
396 {
397  CPNotification _notification;
398  Function _block;
399 }
400 
401 /* @ignore */
402 - (id)initWithBlock:(Function)aBlock notification:(CPNotification)aNotification
403 {
404  self = [super init];
405 
406  if (self)
407  {
408  _block = aBlock;
409  _notification = aNotification;
410  }
411 
412  return self;
413 }
414 
415 /* @ignore */
416 - (void)main
417 {
418  _block(_notification);
419 }
420 
421 /* @ignore */
422 - (BOOL)isReady
423 {
424  return YES;
425 }
426 
427 @end
Used to implement exception handling (creating & raising).
Definition: CPException.h:2
id init()
Definition: CALayer.j:126
An object representation of nil.
Definition: CPNull.h:2
var CPNotificationDefaultCenter
void raise:reason:(CPString aName, [reason] CPString aReason)
Definition: CPException.j:66
A mutable key-value pair collection.
Definition: CPDictionary.h:2
An immutable string (collection of characters).
Definition: CPString.h:2
CPNull null()
Definition: CPNull.j:51
A notification that can be posted to a CPNotificationCenter.
Definition: CPNotification.h:2
CPString name
Definition: CPException.j:47
Represents an operation that can be run in an CPOperationQueue.
Definition: CPOperation.h:2
id init()
Definition: CPObject.j:145
Sends messages (CPNotification) between objects.
CPString UID()
Definition: CPObject.j:552
id alloc()
Definition: CPObject.j:130
Represents an operation queue that can run CPOperations.