1

I have a view controller that's subscribing to notifications broadcast elsewhere. This is part of a large object graph that has some cleanup that needs to be done when the children are dealloc'd. But dealloc was never being called in one of the children (ARC environment), which (if I understand) means something somewhere was still retained, thus causing ARC to never dealloc even when the VC is dismissed. I've traced the offending code to the following two lines. The first version causes the VC and children to never be fully dealloc'd. In the second version, things work fine and everything gets dealloc'd when this VC is dismissed.

My question is why? With ARC I can't understand why this additional NSArray would not get properly released.

Hopefully this is enough code. Can post more if need be.

Here's the version that leads to the VC (and children, etc) never being fully dealloc'd:

// Subscribe to notifications that the number of trackables has changed
[[NSNotificationCenter defaultCenter] addObserverForName:kUpdatedNumberofTrackables object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
    if ([note.object isKindOfClass:[NSArray class]]) {
        NSArray *activeTrackableNames = note.object; // <-- offending line
        [self trackableUpdate:activeTrackableNames]; // <-- offending line
    } else {
        NSLog(@"Observer error. Object is not NSArray");
    }
}];

But when I do it the following way, everything gets released properly when the view is unloaded, and thus dealloc is called on child VCs:

// Subscribe to notifications that the number of trackables has changed
[[NSNotificationCenter defaultCenter] addObserverForName:kUpdatedNumberofTrackables object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
    if ([note.object isKindOfClass:[NSArray class]]) {
        [self trackableUpdate:note.object]; // <-- seems to work
    } else {
        NSLog(@"Observer error. Object is not NSArray");
    }
}];

A few other relevant methods in this VC that may(?) play a role:

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    self.trackablesVisible_private = nil;
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

#pragma mark - Target Handling
-(void)trackableUpdate:(NSArray *)trackablesArray {
    NSLog(@"Trackable Changed");
    if (([trackablesArray count] > 0) && [self.delegate respondsToSelector:@selector(foundValidTargets:)]) {
        [delegate foundValidTargets:trackablesArray];
    }
    self.trackablesVisible_private = trackablesArray;
}

I don't understand what's going on here. Could someone please enlighten me?

Thanks!

EDIT:

So as noted in the replies, part of the issue is a retain cycle from failing to use a weak self in the block, however apparently there's another issue going on here because I'm using Notification Center. The solution is described here: Dealloc Not Running When Dismissing Modal View from Block

My final working code is as follows:

In @interface

__weak id observer;

In loadView

__weak ThisViewController *blockSelf = self; // Avoids retain cycle
observer = [[NSNotificationCenter defaultCenter] addObserverForName:kUpdatedNumberofTrackables object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
    if ([note.object isKindOfClass:[NSArray class]]) {
        ThisViewController *strongSelf = blockSelf; // Avoids retain cycle
        NSArray *activeTrackableNames = note.object;
        [strongSelf trackableUpdate:activeTrackableNames];
    } else {
        NSLog(@"Observer error. Object is not NSArray");
    }
}];

And in viewWillDisappear

[[NSNotificationCenter defaultCenter] removeObserver:observer];

I don't yet fully understand why you have to save the returned observer object, rather than just removing self, but this works.

Community
  • 1
  • 1
Murdock
  • 826
  • 9
  • 21
  • Can you try `__block TypeOfSelf *selfRef = self;` and use `selfRef` inside the block. Also replace `TypeOfSelf` per the type of self's class. – Rui Peres Apr 19 '13 at 22:01
  • It REALLY WORKS even with iOS 12! Strange but this solution works best to kill nasty hanging notification observers...even there is no clear explanation. It saved me a lot of extra works... – BootMaker Sep 26 '18 at 14:49

1 Answers1

2

You have a retain cycle in your block...here is what you want

__weak MyViewControllerName *bSelf = self;
// Subscribe to notifications that the number of trackables has changed
[[NSNotificationCenter defaultCenter] addObserverForName:kUpdatedNumberofTrackables object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
    if ([note.object isKindOfClass:[NSArray class]]) {
        **NSArray *activeTrackableNames = note.object;
        [bSelf trackableUpdate:activeTrackableNames];**
    } else {
        NSLog(@"Observer error. Object is not NSArray");
    }
}];

For more info on blocks and retain cycles, check out http://zearfoss.wordpress.com/2012/05/11/a-quick-gotcha-about-blocks/

JonahGabriel
  • 3,066
  • 2
  • 18
  • 28
  • Thanks, this was part of the issue. The other was that you have to save the observer object to unsubscribe from it specifically later or the VC also stays retained. See my edit for code. – Murdock Apr 22 '13 at 18:15