forked from cappuccino/cappuccino
/
CPNotificationCenter.j
356 lines (298 loc) · 10.9 KB
/
CPNotificationCenter.j
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/*
* 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 && (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