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.