NSNotificationCenter posting notifications doesn't work

251 Views Asked by At

I have a func which uses the nw_path_monitor_t to register for network events.

// Entry point.
// Will be called from AppDelegate when app starts up
void TestNWPathMonitor () {
    
    PrintToFile("TestingNWPathMonitor\n");
    
    NotificationReceiver *notification_receiver = [[NotificationReceiver alloc] init];
    
    // Set up the notification receiver to listen for wifi notification
    [notification_receiver RegisterNotification];
    
    monitor = nw_path_monitor_create ();
    nw_path_monitor_set_update_handler (monitor, WifiNetworkChangeCB);
    nw_path_monitor_start(monitor);
}

I've provided the callback, which will be invoked when there is a change in network events. In the callback (as shown below), I'm looking out for wifi events and posting a notification to the default notification center.

nw_path_monitor_update_handler_t WifiNetworkChangeCB = ^ (nw_path_t path) {
    
    PrintToFile("Wifi Network change!!\n");
    nw_path_status_t status = nw_path_get_status (path);
    
    if (nw_path_uses_interface_type (path, nw_interface_type_wifi)) {
        if (status == nw_path_status_satisfied) {
            PrintToFile("nw_path_status_satisfied\n");
            [[NSNotificationCenter defaultCenter] postNotificationName:@"WifiNetworkChange" object:nil];
        } else {
            PrintToFile("!(nw_path_status_satisfied)\n");
        }
    }
};

This is the NotificationReceiver class:

// NotificationReceiver.h
#include    <Foundation/Foundation.h>


@interface NotificationReceiver : NSObject

- (void) HandleNotification : (NSNotification *) pNotification;
- (void) RegisterNotification ;

@end

// NotificaitonReceiver.m
@implementation NotificationReceiver
    
- (void) HandleNotification : (NSNotification *) pNotification {
    
    PrintToFile([[NSString stringWithFormat:@"Received notification: %@\n", pNotification.name] UTF8String]);
    
}

- (void) RegisterNotification {
    
    PrintToFile("RegisterNotification!\n");
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(HandleNotification:) name:@"WifiNetworkChange" object:nil];
}

@end

RegisterNotification, called at the beginning (as shown in the first code snippet) will add the instance as an observer and HandleNotification is the receiver of the wifi notification posted from the WifiNetworkChangeCB block.

The problem is, when I receive the wifi event, the WifiNetworkChangeCB is invoked and the postNotificationName function is executed (Have verified with the debugger), but HandleNotification doesn't receive the notification.

I'm getting the following output:

TestingNWPathMonitor
RegisterNotification!
Wifi Network change!!

Whereas, the expected output is:

TestingNWPathMonitor
RegisterNotification!
Wifi Network change!!
Received notification: WifiNetworkChange

I have read the documentation of the notification center to understand its usage. Have also referred this answer. I have also referred the documentation of the funcs I'm using (added them as hyperlinks while explaining the problem), everything seems fine.

But I'm obviously missing something (Since it didn't work). Any help will be greatly appreciated.

1

There are 1 best solutions below

1
On BEST ANSWER

Reason: your C function TestNWPathMonitor() allocates NotificationReceiver *notification_receiver but the object created is nowhere stored when you leave the scope. So with ARC memory management the object will be released when the scopes block is left, aka its stack is "empty" again.

your monitor aka typedef NSObject<OS_nw_path_monitor> *nw_path_monitor_t; seems to be a global so it will still exist after leaving the scope giving you possibly the misconception that same would be the case for objc allocations, well yes and no. Same would have happend to monitor if it would have been a local variable.

Debugging: Observering [NSNotificationCenter defaultCenter] allows you to catch Notifications almost anywhere in your code, no matter what thread you wait for them, its a NSString based API for good reason with all its pro's and cons. Because of that easy approach it can be hard to find why it is not working. But basically placing an Observer in main.m or APPDelegate should always tell you if it is properly working on the posting side to be sure you did not miss-spell the NotificationName. To avoid the latter case we often declare

extern NotificationName const kSomeNiceNotification;
// in .h && the following in .m
NotificationName const kSomeNiceNotification = @"kSomeNiceNotification";

and use this global key instead as name.

Hint: you can also create a single time Notification that will be triggered and destroy itself when received with other implications you have to think of when doing so. like so (from the Xcode docs)..

NSNotificationCenter * __weak center = [NSNotificationCenter defaultCenter];
id __block token = [center addObserverForName:@"OneTimeNotification"
                                       object:nil
                                        queue:[NSOperationQueue mainQueue]
                                   usingBlock:^(NSNotification *note) {
                                       NSLog(@"Received the notification!");
                                       [center removeObserver:token];
                                   }];

See [NSOperationQueue mainQueue] in above code snippet? You could pass nil there but then the notifications block would execute on the thread the notification was send. When used in UI code, which most often is the usecase for Notifications this is important as UI tasks needs to be executed on the main thread where passing nil forces you later on to wrap UI stuff in

dispatch_async(dispatch_get_main_queue(), ^{ /* UI code block */ }); // or
dispatch_sync(dispatch_get_main_queue(), ^{ /* UI code block */ });

which you don't need to do when you told the notification observer where to execute the notifications block when received.

Ps: in objc we start methodnames in small letters, you will run in trouble when setter & getters violate the "camelCased" methodname rule because the interface
@property NSObject *someName; becomes
-(NSObject*)someName; as getter and -(void)setSomeName:(NSObject*)somename; as setter with modern objc. This tells also why we use the lower underscore to mark local class variables that are the counterparts of almost any property.. in this given example the property NSObject *someName would have an internal _someName counterpart. Not going deeper here as in oldschool objc there is more to know about the class declarations @dynamic ... & @synthesize ... that allow more detailed control of the (internal) local class variable name.
Why to bother about that? Your NotificationReceiver *notification_receiver could override a class property with the same name giving you the impression you made everything right but is still not working as the declaration would still leave the stack empty then. So declaring the variable like _notification_receiver = ... in the methods/function block would make very clear you meant the internal counterpart of its @property NotificationReceiver *notification_receiver; and not a extra local variable.