Patrick Perini
Patrick Perini

Reputation: 22633

NSNotificationCenter - Observing for Notification Name with Multiple Methods

So, I have an object that has methods to toggle watching for a certain notification name, like so:

- (void)startWatchingForA
{
    [[NSNotificationCenter defaultCenter] addObserver: self
                                             selector: @selector(handleA:)
                                                 name: SomeNotificationName
                                               object: nil];
}

- (void)stopWatchingForA
{
    [[NSNotificationCenter defaultCenter] removeObserver: self
                                                    name: SomeNotificationName
                                                  object: nil];
}

Which works fine. However, I have another method, handleB:, that needs to respond to the same notification.

- (void)startWatchingForB
{
    [[NSNotificationCenter defaultCenter] addObserver: self
                                             selector: @selector(handleB:)
                                                 name: SomeNotificationName
                                               object: nil];
}

- (void)stopWatchingForB
{
    [[NSNotificationCenter defaultCenter] removeObserver: self
                                                    name: SomeNotificationName
                                                  object: nil];
}

The issue is that, should stopWatchingA or stopWatchingB be called, the object will stop watching for both. Is there some way to remove one observation instance, but not the other?

Ideally, when I call stopWatchingForA, I want handleA: to not be called, entirely independent of B.

Upvotes: 1

Views: 3036

Answers (2)

Patrick Perini
Patrick Perini

Reputation: 22633

So blocks and mikeash, once again, ended up saving my day!

One thing that I omitted in my original question was that I would like for this paradigm to be category-safe, which meant no ivars or properties as flags. Here's what I ended up doing:

#import <objc/runtime.h>

static void *AHandlerKey;
static void *BHandlerKey;

- (void)startWatchingForA
{
    // initialize `void (^aBlock)(NSNotification *)` block
    id AHandler = [[NSNotificationCenter defaultCenter] addObserverForName: SomeNotificationName
                                                                    object: nil
                                                                     queue: nil
                                                                usingBlock: aBlock];

    objc_setAssociatedObject(self, AHandlerKey, AHandler, OBC_ASSOCIATION_RETAIN);
}

- (void)stopWatchingForA
{
    id AHandler = objc_getAssociatedObject(self, AHandlerKey);
    [[NSNotificationCenter defaultCenter] removeObserver: AHandler
                                                    name: SomeNotificationName
                                                  object: nil];
}

- (void)startWatchingForB
{
    // initialize `void (^bBlock)(NSNotification *)` block
    id BHandler = [[NSNotificationCenter defaultCenter] addObserverForName: SomeNotificationName
                                                                    object: nil
                                                                     queue: nil
                                                                usingBlock: bBlock];

    objc_setAssociatedObject(self, BHandlerKey, BHandler, OBC_ASSOCIATION_RETAIN);
}

- (void)stopWatchingForB
{
    id BHandler = objc_getAssociatedObject(self, BHandlerKey);
    [[NSNotificationCenter defaultCenter] removeObserver: BHandler
                                                    name: SomeNotificationName
                                                  object: nil];
}

This way, AHandler receives notifications, while aBlock is executed, and BHandler/bBlock likewise. When I remove AHandler as an observer, BHandler is unaffected, and vice versa. Perfect!

Update: Big thanks to Josh Caswell who suggested the use of object association!

Upvotes: 1

jscs
jscs

Reputation: 64002

How about a slight redesign? Only one method to receive the notification, and two flags to indicate what you want to do with it.

@implementation ThisObject
{
   BOOL isWatchingForA;
   BOOL isWatchingForB;
}

- (void) registerForNotifications {

    [[NSNotificationCenter defaultCenter] addObserver: self
                                         selector: @selector(handleNotification:)
                                             name: SomeNotificationName
                                           object: nil];
}

- (void) deregisterForNotifications {
    if( !isWatchingA && !isWatchingB ){
        [[NSNotificationCenter defaultCenter] removeObserver: self
                                                name: SomeNotificationName
                                              object: nil];
    }
}


- (void) startWatchingForA {
    isWatchingForA = YES;
}
- (void) stopWatchingForA {
    isWatchingForA = NO;
}

- (void) startWatchingForB {
    isWatchingForB = YES;
}
- (void) stopWatchingForB {
    isWatchingForB = NO;
}

- (void) handleNotification: (NSNotification *)note {

    if( isWatchingForA ){
        [self handleA:note];
    }
    if( isWatchingB ){
        [self handleB:note];
    }
}

//...

@end

Upvotes: 2

Related Questions