Reputation: 22633
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
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
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