Be Careful Using NSNotificationCenter With Blocks

NSNotificationCenter is a long existing mechanism for broadcasting messages to zero or many listeners. Many of Apple’s frameworks work deeply by notifiying you via an NSNotification when a message is posted. Traditionally, the workflow has been to follow a pattern similar to this:

Simple Receiving Notification Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)viewDidLoad
{
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(someMethod:) name:kMyNotificationIdentifier object:nil];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)someMethod:(NSNotification *)note
{
    // Message received
}

Blocks!

As of iOS 4 and the introduction of blocks, Mac and iOS developers can now use blocks to subscribe to NSNotification broadcasts. This new method also allows you to specify an NSOperationQueue to perform the block action on.

Block-based NSNotificationCenter
1
2
3
4
5
6
7
- (void)viewDidLoad
{
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserverForName:kMyNotificationIdentifier object:nil queue:nil usingBlock:^(NSNotification *note) {
        // message received
    }];
}

Wait, what?

Not so fast! Who is the observer? What removes this observer? What if you load several view controllers that do this? This block based method is not as simple as it appears. Reading the docs, we learn that addObserverForName:object:queue:usingBlock: actually returns an opaque observer that you are meant to retain, and subsequently -removeObserver with. Let’s take a look at what this looks like.

Block-based NSNotificationCenter Properly
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@implementation MyViewController
{
    id _notificationObserver;
}

// ...

- (void)viewDidLoad
{
    [super viewDidLoad];
    _notificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMyNotificationIdentifier object:nil queue:nil usingBlock:^(NSNotification *note) {
        // message received
    }];
}

// dealloc, or potentially a method popping this view from the stack
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:_notificationObserver];
}

As you can see, you still need to track the observer of a notification and remove it. Similar to what you would do if you were using the selector-based notification listener. Oddly enough, this is an example of a block-based API not really improving things. For this API, the selector-based NSNotificationCenter listener is a much simpler option as you don’t have to maintain the observer seperately.


Comments