1

On Cocoa code with ARC enabled, I tried to observe Window closing events like below.

ScanWindowController * c = [[ScanWindowController alloc] initWithWindowNibName:@"ScanWindowController"];
[scanWindowControllers addObject:c];
[c showWindow:nil];

NSMutableArray *observer = [[NSMutableArray alloc] init];
observer[0] = [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
    [scanWindowControllers removeObject:c];
    [[NSNotificationCenter defaultCenter] removeObserver:observer[0]];
}];

I thought this will remove all the references to the controller (c) after closing Window. But actually, this code does not dealloc ScanWindowController after closing Window. If I wrote like below using weak reference to the controller, dealloc of ScanWindowController is called.

ScanWindowController * c = [[ScanWindowController alloc] initWithWindowNibName:@"ScanWindowController"];
[scanWindowControllers addObject:c];
[c showWindow:nil];

__weak ScanWindowController * weak_c = c;
NSMutableArray *observer = [[NSMutableArray alloc] init];
observer[0] = [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
    [scanWindowControllers removeObject:weak_c];
    [[NSNotificationCenter defaultCenter] removeObserver:observer[0]];
}]; 

Why does the first code not work?

sawa
  • 165,429
  • 45
  • 277
  • 381
Tsuneo Yoshioka
  • 7,504
  • 4
  • 36
  • 32

1 Answers1

3

I think the retain cycle is between the observer array and the block. The observer array holds the actual observer object. So long as the observer object is alive, it holds the block. The block holds the observer array.

This keeps the ScanViewController as a side effect. I see no evidence that the ScanViewController holds a strong reference to the observer.

I believe the solution is to remove the observer from the observer array at the end of the block. Another solution would be to not use an array to hold the observer, just a __block id variable. Then, set that variable to nil at the end of the block.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • **observer** is local variable and ARC is enabled. So, at the end of function, **observer** array and contents should be released ? – Tsuneo Yoshioka Sep 02 '13 at 10:56
  • 1
    ARC does **not** guarantee that an object referenced by a local variable is destroyed when that local goes out of scope. *One* of the references will be released, but that doesn't mean the object is deallocated. The block holds another reference to it, and the block is kept as long as the observation object (the one returned by `-addObserverForName:object:queue:usingBlock:`) exists. That observation object exists as long as the `observer` array exists, which is the retain cycle. – Ken Thomases Sep 02 '13 at 12:24
  • Ah, I got it ! So, I wrote like **arr = @[];arr[0]=arr;**. Just calling **[observer removeAllObjects]** at the end of block solved the reference loop. Thanks ! – Tsuneo Yoshioka Sep 02 '13 at 15:26
  • "So long as the observer object is alive, it holds the block." Is this documented somewhere? – user102008 Dec 31 '13 at 01:28
  • @user102008, not that I can find. The docs say "The block is copied by the notification center and (the copy) held until the observer registration is removed." So, it's clear that the block copy will live at least as long as the observer is registered. It suggests but does not guarantee that it will be released when the registration is removed, but apparently it's not. – Ken Thomases Dec 31 '13 at 02:08