简体   繁体   中英

Why does this NSArray in an NSNotification block (apparently) never get released in an ARC environment?

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.

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/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM