88
99#import < monalxmpp/MLNotificationQueue.h>
1010
11+ @interface ObserverEntry : NSObject
12+ @property (nonatomic , weak ) id observer;
13+ @property (nonatomic ) SEL selector;
14+ @property (nonatomic , strong ) NSMethodSignature * sig;
15+ @property (nonatomic , strong ) IMP imp;
16+ @property (nonatomic , weak ) id object;
17+ @end
18+ @implementation ObserverEntry
19+ -(NSString *) description
20+ {
21+ return [NSString stringWithFormat: @" ObserverEntry: selector %@ on %@ " , self .selector, self .observer];
22+ }
23+ @end
24+
1125@interface MLNotificationQueue ()
1226{
1327 NSString * _queueName;
1428 NSMutableArray * _entries;
1529 id _lowerQueue; // use id because this could be an MLNotificationQueue *or* [NSNotificationCenter defaultCenter]
30+ // for direct delivery without queueing
31+ NSMutableDictionary <NSNotificationName , NSMutableArray <ObserverEntry*>*>* _directObservers;
1632}
1733+(NSMutableArray *) getThreadLocalNotificationQueueStack ;
1834@end
@@ -39,6 +55,7 @@ +(void) queueNotificationsInBlock:(monal_void_block_t) block onQueue:(NSString*)
3955 // flush the queue to the next queue in our stack (or send them to the notification center if no queue is left on the stack)
4056 // don't use the flush deallocate because we want our flush to be "inline" thread-wise
4157 [queue flush ];
58+ [queue bubbleUpDirectObservers ];
4259 // this will deallocate our queue (flushing was already done before)
4360 queue = nil ;
4461}
@@ -55,17 +72,47 @@ +(instancetype) currentQueue
5572-(void ) postNotificationName : (NSNotificationName ) notificationName object : (id _Nullable) notificationObject userInfo : (id _Nullable) notificationUserInfo
5673{
5774 DDLogDebug (@" Queueing notification: %@ , object = %@ , userInfo = %@ " , notificationName, notificationObject, notificationUserInfo);
58- // create queue entry (handle nil arguments)
59- NSMutableDictionary * entry = [NSMutableDictionary new ];
60- entry[@" name" ] = notificationName;
61- if (notificationObject != nil )
62- entry[@" obj" ] = notificationObject;
63- if (notificationUserInfo != nil )
64- entry[@" userInfo" ] = notificationUserInfo;
65-
66- // add entry to our queue
75+ NSNotification * notification = [NSNotification notificationWithName: notificationName object: notificationObject userInfo: notificationUserInfo];
6776 @synchronized (_entries) {
68- [_entries addObject: entry];
77+ [_entries addObject: notification];
78+ }
79+
80+ // allow observers in the same notification queue to receive the notification immediately rather than on queue flush
81+ NSArray * observers = @[];
82+ @synchronized (_directObservers) {
83+ if (_directObservers[notificationName] != nil )
84+ observers = [_directObservers[notificationName] copy ];
85+ }
86+ if (observers.count > 0 )
87+ {
88+ MLNotificationQueue* currentQueue = [MLNotificationQueue currentQueue ];
89+ DDLogDebug (@" Calling direct handlers for notification created in the same queue (%@ ): %@ " , currentQueue.name , notificationName);
90+ for (let entry in observers[notificationName])
91+ {
92+ if (entry.observer == nil )
93+ continue ;
94+ if (entry.object != nil && entry.object != notificationObject)
95+ continue ;
96+ if (![entry.observer respondsToSelector: entry.selector])
97+ continue ;
98+
99+ switch (entry.sig .numberOfArguments )
100+ {
101+ case 2 :
102+ // -(void) handler;
103+ void (*func)(id , SEL ) = (void (*)(id , SEL ))entry.imp ;
104+ func (entry.observer , entry.selector );
105+ break ;
106+ case 3 :
107+ // -(void) handler:(NSNotification*);
108+ void (*func)(id , SEL , NSNotification *) = (void (*)(id , SEL , NSNotification *))entry.imp ;
109+ func (entry.observer , entry.selector , notification);
110+ break ;
111+ default :
112+ unreachable (@" We should never reach this because of previous sanity checks!" );
113+ break ;
114+ }
115+ }
69116 }
70117}
71118
@@ -81,6 +128,54 @@ -(void) postNotification:(NSNotification*) notification
81128 [self postNotificationName: notification.name object: notification.object userInfo: notification.userInfo];
82129}
83130
131+ // this is compatible to [NSNotificationCenter defaultCenter]
132+ -(void ) addObserver : (id ) observer selector : (SEL ) aSelector name : (NSNotificationName ) aName object : (id ) anObject
133+ {
134+ if (!observer || !aSelector || !aName)
135+ return ;
136+
137+ ObserverEntry* entry = [ObserverEntry new ];
138+ entry.observer = observer;
139+ entry.selector = aSelector;
140+ entry.object = anObject;
141+ entry.imp = [observer methodForSelector: aSelector];
142+ entry.sig = [observer methodSignatureForSelector: aSelector];
143+
144+ // sanity checks (do them here to have higher performance when calling handlers later)
145+ if (entry.sig .methodReturnType != " v" )
146+ @throw [NSException exceptionWithName: @" NotificationQueueException" reason: @" Tried to use notification handler with non-void return value!" userInfo: @{
147+ @" sig" : sig,
148+ @" sig.methodReturnType" : @(sig.methodReturnType ),
149+ }];
150+ if (sig.numberOfArguments > 3 || sig.numberOfArguments < 2 )
151+ @throw [NSException exceptionWithName: @" NotificationQueueException" reason: @" Tried to use notification handler with unsupported argument count!" userInfo: @{
152+ @" sig" : sig,
153+ @" sig.numberOfArguments" : @(sig.numberOfArguments ),
154+ }];
155+
156+ @synchronized (_directObservers) {
157+ if (_directObservers[aName] == nil )
158+ _directObservers[aName] = [NSMutableArray new ];
159+ [_directObservers[aName] addObject: entry];
160+ }
161+ }
162+
163+ // this is compatible to [NSNotificationCenter defaultCenter]
164+ -(void ) removeObserver : (id ) observer
165+ {
166+ @synchronized (_directObservers) {
167+ for (let name in _directObservers)
168+ {
169+ for (let entry in [_directObservers[name] copy ])
170+ if (entry.observer == observers)
171+ {
172+ DDLogDebug (@" Removing observer for notification %@ : %@ " , name, entry);
173+ [_directObservers[name] removeObject: observer];
174+ }
175+ }
176+ }
177+ }
178+
84179-(NSUInteger ) flush
85180{
86181 DDLogDebug (@" Flushing queue '%@ ', current stack: %@ " , [self name ], [[[[self class ] getThreadLocalNotificationQueueStack ] reverseObjectEnumerator ] allObjects ]);
@@ -91,7 +186,7 @@ -(NSUInteger) flush
91186 }
92187 DDLogVerbose (@" Notifications in queue '%@ ': %@ " , [self name ], toFlush);
93188 for (NSDictionary * entry in toFlush)
94- [_lowerQueue postNotificationName: entry[ @" name" ] object: entry[ @" obj " ] userInfo: entry[ @" userInfo" ] ];
189+ [_lowerQueue postNotificationName: entry. name object: entry.object userInfo: name. userInfo];
95190 @synchronized (_entries) {
96191 if ([_entries count ])
97192 @throw [NSException exceptionWithName: @" NotificationQueueException" reason: [NSString stringWithFormat: @" Tried to add more entries to queue while flushing: %@ " , _queueName] userInfo: nil ];
@@ -121,7 +216,7 @@ -(NSString*) description
121216 NSMutableArray * queuedNotificationNames = [NSMutableArray new ];
122217 @synchronized (_entries) {
123218 for (NSDictionary * entry in _entries)
124- [queuedNotificationNames addObject: entry[ @" name" ] ];
219+ [queuedNotificationNames addObject: entry. name];
125220 }
126221 return [NSString stringWithFormat: @" %@ : %@ " , self .name, queuedNotificationNames];
127222}
@@ -141,14 +236,40 @@ -(instancetype) initWithName:(NSString*) queueName
141236 _queueName = queueName;
142237 _entries = [NSMutableArray new ];
143238 _lowerQueue = [MLNotificationQueue currentQueue ];
239+ _directObservers = [NSMutableDictionary new ];
144240 return self;
145241}
146242
243+ -(void ) bubbleUpDirectObservers
244+ {
245+ /* NSArray* observers = @[];
246+ @synchronized(_directObservers) {
247+ if(_directObservers[notificationName] != nil)
248+ {
249+ observers = [_directObservers[notificationName] copy];
250+ _directObservers = [NSMutableDictionary new];
251+ }
252+ }
253+ if(observers.count > 0)
254+ {
255+ MLNotificationQueue* currentQueue = [MLNotificationQueue currentQueue];
256+ DDLogDebug(@"Promoting direct all handlers to next queue: %@", currentQueue);
257+ for(let name in observers)
258+ for(let entry in observers[name])
259+ if(entry.observer != nil)
260+ [currentQueue addObserver:entry.observer selector:entry.selector name:name object:entry.object];
261+ }*/
262+ }
263+
147264-(void ) dealloc
148265{
149266 // there should only be one thread calling dealloc ever (per objc runtime) --> no @synchronized needed
150267 if ([_entries count ])
151268 [self flush ];
269+
270+ // bubble up all observers still registered with us
271+ if ([_directObservers count ])
272+ [self bubbleUpDirectObservers ];
152273}
153274
154275@end
0 commit comments