37

Is it possible to get the list of observers (objects and selectors) for a given notification name? (NSNotificationCenter)

Antoine Rosset
  • 1,045
  • 2
  • 13
  • 22

7 Answers7

29

(iOS 9, Swift 3) If you want to find out which observers are currently registered in NotificationCenter, break and print its debug description:

(lldb) e print(NotificationCenter.default.debugDescription)

Each line of the output will contain (Notification) Name, Object, Observer, Options. Multiple calls to NotificationCenter.default.addObserver with some NSNotification.Name will result in multiple entries in this list.


NB. while this might prove useful information when debugging, I would not advise to manage observers at runtime using this output.


(source: answer based on useyourloaf)

PDK
  • 1,476
  • 1
  • 14
  • 25
16

I don't think there is an (official) way of retrieving the list of observers for a given notification name from NSNotificationCenter. However, you could create a subclass of NSNotificationCenter and then override the following methods:

  • + defaultCenter
  • - addObserver:selector:name:object
  • - addObserverForName:object:queue:usingBlock:
  • - removeObserver:
  • - removeObserver:name:object

In the overriding implementations of the instance methods, you would then keep track of the observers for a given notification name using a dictionary. In each overridden instance method you would finally call NSNotificationCenter's respective super method. Additionally, you would provide a method to retrieve your own list of observers for the given name, for example:

- (id)observerForNotificationName:(NSString *)name

However, there are two issues with this approach: first, NSMutableDictionary would retain all observers in a naive implementation, which is probably not the same behavior NSNotificationCenter implements. Second, you would have to change the code that gets the default notification center by [NSNotificationCenter defaultCenter] (or any other NSNotificationCenter instance) so as to use your custom subclass.

Note that the first issue is solvable using a CFDictionary with weak reference callbacks, a container class with a weak reference to the respective observer, or, if you are in a garbage collected environment on Mac OS X, an NSHashTable.

Community
  • 1
  • 1
starbugs
  • 992
  • 1
  • 9
  • 14
5

There is no public API to query NSNotificationCenter about the list of current observers for any object or notification.

The previous answer outlines a solution and goes to some level of detail regarding the ownership of observers, in a subclass of NSNotificationCenter designed to collect and provide such information.

However, this solution can only be used with your own code, that will call the subclass of NSNotiicationCenter. What about other code, both in the system and external libraries who use the base NSNotificationCenter for registering/unregistering for notifications?

I suggest instead of subclassing NSNotificationCenter, using a bit of low-level ObjC to swizzle the method implementations of original NSNotifictionCenter, replacing them with our own implementations, that will work more-or-less as described in the previous answer, and will call the original implementations as their last act.

Here's how to do this: http://nshipster.com/method-swizzling/

Then, you can be sure you get ALL the observers of some notification, and that your code is portable and usable with 3rd party code that directly uses NSNotificationCenter.

Motti Shneor
  • 2,095
  • 1
  • 18
  • 24
1

Instead of using NSNotificationCenter, you can try this ObserversCenter. And you can get the list of observers.

About ObserverCenter:

  1. it implements an multi-observer pattern as NSNotificationCenter;
  2. it decouple the observed and observers, so they don't know each other;
  3. you can subscribe on a specified key;
  4. you can call the real interface when making a notification.
Andrew
  • 1,088
  • 10
  • 21
  • 1
    NSNotificationCenter is also cross-app (process) and has many system-wide behaviors that any 3rd party mechanism cannot imitate. You cannot observe Cocoa and Cocoa-Touch standard notification (e.g. appWillbecomeForeground etc.) using a 3rd party mechanism. So - the preferable way is to extend the existing one with care. – Motti Shneor Nov 09 '19 at 20:11
1

I created a category on NSNotificationCenter and swizzled the addObserver:::: method.

This is only for debugging and should never be in production code as it will lead to retain cycles

@interface NSNotificationCenter (Tracking)
@property (nonatomic) NSMutableArray <NSDictionary *> * observers;
@end


#import <JRSwizzle/JRSwizzle.h>
@implementation NSNotificationCenter (Tracking)

+ (void)initialize {
    [super initialize];
    [self jr_swizzleMethod:@selector(addObserver:selector:name:object:)
                withMethod:@selector(SNaddObserver:selector:name:object:)
                     error:nil];
}

- (void)SNaddObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject {
    NSDictionary *obs = @{@"observer"   :observer,
                          @"selector"   :NSStringFromSelector(aSelector),
                          @"name"       :aName
                          };
    DDLogDebug(@"observer added : %@", obs);
    [[self observers] addObject:obs];
    [self SNaddObserver:observer selector:aSelector name:aName object:anObject];
}

- (NSMutableArray <NSDictionary *> *) observers{
    static NSMutableArray <NSDictionary *> * _observers = nil;
    if (!_observers) {
        _observers = [NSMutableArray new];
    }
    return _observers;
}

@end
Zayin Krige
  • 3,229
  • 1
  • 35
  • 34
  • 1
    with some enhancement, that could become a viable solution. 1) use some CF collection that allows for weak references instead of your NSDictionary - so to not create more references to the observing objects. 2) make your SNAddObserver thread-safe (use dispatch_once for creating your collections) 3) cleanup from time to time, as observers are removed. – Motti Shneor Nov 09 '19 at 19:58
  • @MottiShneor I've been looking for something similar to that weak reference cf collection. Is that really possible possible though? Any resources would be great – pnizzle Dec 06 '19 at 09:19
  • 1
    @pnizzle give NSMapTable a try? https://developer.apple.com/documentation/foundation/nsmaptable – Andrew Jan 27 '20 at 01:40
  • Yes, NSMapTable is a more modern solution to this problem and probably more well suited, as it won't retain the observers – Zayin Krige Jan 28 '20 at 04:52
1

Similar to @PDK's answer, po NotificationCenter.default (or the appropriate instance) will yield debug information:

<NSNotificationCenter:0x600000f84310>
Name, Object, Observer, Options
UIApplicationSimpleRemoteActionNotification, 0x7faac90069a0, 0x600000f84fc0, 1400
NSTextStorageDidProcessEditingNotification, 0x7faacd80d080, 0x600001808a00, 1400
com.apple.ManagedConfiguration.profileListChanged, 0x600000b0e280, 0x600000b0e280, 1400
UIApplicationDidBecomeActiveNotification, 0x7faac90069a0, 0x600000ff75d0, 1400
UIApplicationDidBecomeActiveNotification, 0x7faac90069a0, 0x600001880d00, 1400

This works on Xcode 12, on older versions, the debugDescription may be necessary.

robmathers
  • 3,028
  • 1
  • 26
  • 29
-2

Have you tried the observationInfo property of NSObject?

observationInfo
Returns a pointer that identifies information about all of the observers that are registered with the receiver.
Erwan
  • 3,733
  • 30
  • 25