NSNotificationCenter has a very convenient method for registering a block as an observer. However, getting that code right is very tricky. Let's say we open a window and want some code to run when the window closes. An obvious way would be to use some code like the following:
[[NSNotificationCenter defaultCenter] addObserverForName: NSWindowWillCloseNotification
object: aWindow
queue: nil
usingBlock: ^(NSNotification *note) {
NSLog(@"Window closed.");
}];
The problem with the above code is that we never clean up and remove the observer, which will leak memory. Assuming that we want to run this code exactly once, we might come up with the following idea to remove the observer:
id observer = [[NSNotificationCenter defaultCenter] addObserverForName: NSWindowWillCloseNotification
object: aWindow
queue: nil
usingBlock: ^(NSNotification *note) {
NSLog(@"Window closed.");
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}];
Now that wont work, because the block captures the value of a variable at the time it is created.
But the block is created before observer
is set.
Therefore the value of observer
inside the block is undefined!
The logical conclusion of course is to make the variable a __block
variable.
Then, when the variable is set, the change will be visible inside the block.
__block id observer = [[NSNotificationCenter defaultCenter] addObserverForName: NSWindowWillCloseNotification
object: aWindow
queue: nil
usingBlock: ^(NSNotification *note) {
NSLog(@"Window closed.");
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}];
Are we done yet? No. We just ended up with a retain cycle.
The problem is that the block retains the observer, and the the observer retains the block.
To break this cycle, we must set observer to nil
:
__block id observer = [[NSNotificationCenter defaultCenter] addObserverForName: NSWindowWillCloseNotification
object: aWindow
queue: nil
usingBlock: ^(NSNotification *note) {
NSLog(@"Window closed.");
[[NSNotificationCenter defaultCenter] removeObserver:observer];
observer = nil;
}];
When the variable is set to nil
, the observer will be released, and it will in turn release the block.
Problem solved!
This example shows yet again that you can't blindly trust ARC. Especially when working with blocks, you should always think twice about who retains what.