View controller dealloc not called when using NSNotificationCenter code block method with ARC
Asked Answered
B

3

8

When I use -addObserverForName: object: queue: usingBlock: for NSNotificationCenter in the -viewDidLoad: method of my view controller, the -dealloc method ends up not being called.

(When I remove -addObserverForName: object: queue: usingBlock:, -dealloc is called again.)

Using -addObserver: selector: name: object: doesn't seem to have this problem. What am I doing wrong? (My project is using ARC.)

Below is an example of my implementation, in case I'm doing something wrong here:

[[NSNotificationCenter defaultCenter] addObserverForName:@"Update result"
                                                  object:nil
                                                   queue:nil
                                              usingBlock:^(NSNotification *note) {
                                                  updateResult = YES;
                                              }];

Thanks in advance for any help.

I've tried adding the following (to no avail):

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    if ([self isMovingFromParentViewController]) {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
}
Bigamous answered 2/10, 2012 at 22:9 Comment(0)
D
17

updateResult is an instance variable which prevents the object from being deallocated as it is retained by that block.

In other words, you got a retain cycle. The object retains the block and the block retains the object.

You will need to create a weak or unsafe_unretained reference to that instance and its variable for loosing that relationship.

Add the following prior to your notification block:

__unsafe_unretained YouObjectClass *weakSelf = self;

or (in case you are on iOS5 and above)

__weak YouObjectClass *weakSelf = self;

Then, within that block, reference the object via that new weak reference:

[[NSNotificationCenter defaultCenter] addObserverForName:@"Update result"
                                                  object:nil
                                                   queue:nil
                                              usingBlock:^(NSNotification *note) {
                                                  weakSelf.updateResult = YES;
                                              }];

Please note that retain-cycles are not a bad thing per se. Sometimes you actually want them to happen. But those are instances where you are certain that the cycle will be broken after a specific time (e.g. Animation Blocks). The cycle is broken once the block has executed and is removed from the stack.

Destinee answered 2/10, 2012 at 22:26 Comment(7)
I have other blocks that call on methods like [self updateType:@"someType"]; that have the same problem. Is a retain cycle also happening here? Also, can you be more specific on how to "create a weak or unsafe_unretained reference to that instance and its variable"? I'm still relatively new at this... Thanks!Bigamous
Yes, that will also create a retain cycle. Sometimes such cycles are entirely fine - but only in cases where the block is guaranteed to execute. The retain-cycle will be broken once the block has finished its execution (e.g. animation blocks).Destinee
Do I have to make updateResult a property now? Or is there a way to keep it as an instance variable?Bigamous
This answer helped me huge. I have noticed that there is no such problem when ARC is not used. I wonder why? The object still has to retain self and self has to retain the object.Adhibit
@IPhoneGuy: Ran into same problem. Apple's doc, "Transitioning to ARC Release Notes" has the following quote: "In manual reference counting mode, __block id x; has the effect of not retaining x. In ARC mode, __block id x; defaults to retaining x (just like all other values)." Maybe that helps?Pickering
This will let the dealloc to get called since the observer have no reference to the controller anymore. does the observer stops calling the notification block when the controller gets dealloced? or its call it but the weak ref will be nil and nothin will happen? if its still getting called how we can remove that observer?Bricabrac
Ok the block will gets called but because the weakself will be nil nothin will happen. we still need to remove the observer on the dealloc to stop calling the block for the dealloced controllersBricabrac
C
6

This is very likely because you have a retain cycle.

This is typically the case when your block implicitly retain self, and self retains the block in a way. You will have a retain cycle as each one retains the other one, and their retainCount thus never reach zero.

You should activate the warning -Warc-retain-cycles that will warn you about such issues.

So in your case, you are using the variable updateResult, which I assume is an instance variable, and this implicitly retain self. You should instead use a temporary weak variable to represent self, and use this in your block, so that it does not get retained and you break the retain cycle.

__block __weak typeof(self) weakSelf = self; // weak reference to self, unretained by the block
[[NSNotificationCenter defaultCenter] addObserverForName:@"Update result"
                                              object:nil
                                               queue:nil
                                          usingBlock:^(NSNotification *note) {
                                              // Use weakSelf explicitly to avoid the implicit usage of self and thus the retain cycle
                                              weakSelf->updateResult = YES;
                                          }];
Cosmorama answered 2/10, 2012 at 22:29 Comment(0)
E
0

This is not retain cycle.

NSNotificationCenter hold the block, block is hold self. Because [NSNotificationCenter defaultCenter]is a singleton living in all app life cycle, So it hold self indirect.

Elenoraelenore answered 19/11, 2017 at 16:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.