jbg
jbg

Reputation: 5268

Why does my view controller (presented modally from a storyboard segue) not get released after being dismissed?

My view controller (code below) is presented modally from a storyboard segue (attached to a UIButton). It is then dismissed once one of the (dynamically generated) buttons is tapped. For some reason, it is not subsequently released (has a retain count of 1).

Clearly the first concern would be the two objects (PPAPI and PPObjectCache) which hold this object as a delegate (registered in viewDidLoad), however these both use weak references which will automatically be NULLed by ARC if I can get whatever else is retaining it to release it. I have verified that these objects are not holding a strong reference to this view controller.

I have used Instruments (the Allocations profile) to check the retains/releases of this object, and the report is shown below the code. As you can see, the imbalance is in UIKit or Foundation code, but it’s hard for me to see where exactly, or why.

Can anyone spot the reason why UIKit or Foundation is holding on to my view controller?

#import "PPLoginViewController.h"
#import "PPAppDelegate.h"

@interface PPLoginViewController () {
    NSArray *employeeIDs;
}

@end

@implementation PPLoginViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    PPAPI *api = [PPAPI api];
    [api subscribeToViewsOfType:@"employees" delegate:self];
    [[PPObjectCache cache] subscribeToChangesToObjectsOfType:@"worker" delegate:self];
    [api callMethod:@"main.tasks.get_employees" withParameters:@{} callback:nil];
}

// PPAPI view subscription delegate method
- (void)receivedObjectIDs:(NSArray *)objectIDs forViewType:(NSString *)viewType {
    NSLog(@"%@: GOT VIEW", self);
    dispatch_async(dispatch_get_main_queue(), ^{
        employeeIDs = objectIDs;
        [self reload];
    });
}

// PPObjectCache delegate method
- (void)objectChanged:(id)object {
    dispatch_async(dispatch_get_main_queue(), ^{
        [self reload];
    });
}

- (void)reload {
    [[self.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
        return [evaluatedObject isKindOfClass:[UIButton class]];
    }]] makeObjectsPerformSelector:@selector(removeFromSuperview)];

    UIDevice *currentDevice = [UIDevice currentDevice];
    CGFloat y = 75.f;
    for (NSNumber *employeeID in employeeIDs) {
        NSDictionary *employee = [[PPObjectCache cache] objectOfType:@"worker" withID:employeeID];

        UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        [button setTitle:employee[@"name"] forState:UIControlStateNormal];
        button.titleLabel.font = [UIFont boldSystemFontOfSize:18.f];
        button.tag = [employeeID integerValue];
        button.frame = CGRectMake((self.view.bounds.size.width - 240.f) / 2.f, y, 240.f, currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad ? 60.f : 45.f);
        [button addTarget:self action:@selector(login:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];

        y += currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad ? 75.f : 65.f;
    }
}

// UIButton action from above
- (void)login:(UIButton *)sender {
    PPAppDelegate *delegate = (PPAppDelegate *)[UIApplication sharedApplication].delegate;
    delegate.loggedInEmployee = [[PPObjectCache cache] objectOfType:@"worker" withID:@(sender.tag)];
    // self.logoutButton is an @property (weak) set from the presenting view controller
    self.logoutButton.title = [NSString stringWithFormat:@"Logout %@", delegate.loggedInEmployee[@"initials"]];

    [self dismissViewControllerAnimated:YES completion:nil];
}

@end

Allocations report follows (sorry for poor formatting). As you can see, the events for which my application code is responsible are all balanced (show as Retain/Release groups or are immediately followed by balancing calls). Something in UIKit or Foundation isn’t releasing my view controller!

#   Event Type  ∆ RefCt RefCt   Timestamp   Responsible Library Responsible Caller
0   Malloc  +1  1   00:19.062.502   UIKit   -[UIClassSwapper initWithCoder:]
1   Retain  +1  2   00:19.062.991   UIKit   -[UIRuntimeConnection initWithCoder:]
2   Retain  +1  3   00:19.063.036   UIKit   -[UIRuntimeConnection initWithCoder:]
3   Retain  +1  4   00:19.063.230   UIKit   UINibDecoderDecodeObjectForValue
4   Retain  +1  5   00:19.063.274   UIKit   UINibDecoderDecodeObjectForValue
5   Retain  +1  6   00:19.063.315   Foundation  -[NSObject(NSKeyValueCoding) setValue:forKey:]
6   Retain  +1  7   00:19.063.388   UIKit   -[UINib instantiateWithOwner:options:]
    Release (2) -2      00:19.063.568   UIKit   -[UINibDecoder finishDecoding]
8   Release -1  5   00:19.063.599   UIKit   -[UINibDecoder finishDecoding]
10  Release -1  3   00:19.063.722   UIKit   -[UIRuntimeConnection dealloc]
11  Release -1  2   00:19.063.755   UIKit   -[UIRuntimeConnection dealloc]
12  Retain  +1  3   00:19.063.920   UIKit   -[UIStoryboardSegue initWithIdentifier:source:destination:]
    Retain/Release (2)          00:19.067.387   Purchase    -[PPMasterViewController prepareForSegue:sender:]
15  Retain  +1  4   00:19.072.460   UIKit   -[UIViewController setChildModalViewController:]
16  Retain  +1  5   00:19.076.305   UIKit   -[UINib instantiateWithOwner:options:]
17  Retain  +1  6   00:19.076.366   UIKit   -[UINib instantiateWithOwner:options:]
18  Retain  +1  7   00:19.076.582   UIKit   -[UIProxyObject initWithCoder:]
19  Retain  +1  8   00:19.076.587   UIKit   -[UIRuntimeConnection initWithCoder:]
20  Retain  +1  9   00:19.080.167   UIKit   UINibDecoderDecodeObjectForValue
21  Retain  +1  10  00:19.080.230   UIKit   UINibDecoderDecodeObjectForValue
22  Release -1  9   00:19.080.449   UIKit   -[UINib instantiateWithOwner:options:]
23  Release -1  8   00:19.080.507   UIKit   -[UINib instantiateWithOwner:options:]
24  Release -1  7   00:19.080.578   UIKit   -[UINibDecoder finishDecoding]
    Release (2) -2      00:19.080.602   UIKit   -[UINibDecoder finishDecoding]
26  Release -1  5   00:19.080.716   UIKit   -[UIRuntimeConnection dealloc]
    Retain/Release (2)          00:19.081.788   Purchase    -[PPAPI subscribeToViewsOfType:delegate:]
    Retain/Release (2)          00:19.081.866   Purchase    -[PPObjectCache subscribeToChangesToObjectsOfType:delegate:]
32  Retain  +1  5   00:19.089.975   UIKit   -[UIWindow _setRotatableClient:toOrientation:updateStatusBar:duration:force:isRotating:]
33  Retain  +1  6   00:19.091.352   UIKit   __91-[UIWindowController transition:fromViewController:toViewController:target:didEndSelector:]_block_invoke_0238
    Retain/Release (2)          00:19.105.379   UIKit   -[UIResponder becomeFirstResponder]
36  Retain  +1  7   00:19.106.122   UIKit   -[UIViewController presentViewController:withTransition:completion:]
37  Retain  +1  8   00:19.106.142   UIKit   -[UIViewController presentViewController:withTransition:completion:]
38  Release -1  7   00:19.108.717   UIKit   _UIApplicationHandleEvent
39  Release -1  6   00:19.109.517   UIKit   -[UIStoryboardSegue dealloc]
40  Release -1  5   00:19.109.534   UIKit   _UIApplicationHandleEvent
41  Release -1  4   00:19.109.581   UIKit   -[UIStoryboardScene dealloc]
    Retain/Autorelease/Release (5)  +1      00:19.145.293   Foundation  -[NSConcreteHashTable countByEnumeratingWithState:objects:count:]
    Retain (2)  +2      00:19.151.847   Purchase    -[PPLoginViewController receivedObjectIDs:forViewType:]
    Retain (2)  +2      00:19.151.874   Purchase    __copy_helper_block_
    Release (2) -2      00:19.151.888   Purchase    -[PPLoginViewController receivedObjectIDs:forViewType:]
    Release (2) -2      00:19.278.813   Purchase    __destroy_helper_block_
48  Release -1  4   00:19.541.189   UIKit   -[UIWindowController transitionViewDidComplete:fromView:toView:removeFromView:]
    Retain/Release (2)          00:19.996.260   UIKit   -[UIViewController _dismissViewControllerWithTransition:from:completion:]
51  Release -1  3   00:19.996.269   UIKit   -[UIViewController _dismissViewControllerWithTransition:from:completion:]
52  Release -1  2   00:19.996.302   UIKit   -[UIPeripheralHost(UIKitInternal) _stopPinningInputViewsOnBehalfOfResponder:]
53  Retain  +1  3   00:19.996.776   UIKit   -[UIViewController _dismissViewControllerWithTransition:from:completion:]
54  Retain  +1  4   00:20.001.379   UIKit   __91-[UIWindowController transition:fromViewController:toViewController:target:didEndSelector:]_block_invoke_0238
55  Release -1  3   00:20.002.196   UIKit   -[UIViewController _dismissViewControllerWithTransition:from:completion:]
56  Retain  +1  4   00:20.432.653   UIKit   -[UIViewController _didFinishDismissTransition]
57  Release -1  3   00:20.432.658   UIKit   -[UIViewController setChildModalViewController:]
58  Release -1  2   00:20.432.662   UIKit   -[UIViewController _didFinishDismissTransition]
59  Release -1  1   00:20.432.706   UIKit   -[UIWindowController transitionViewDidComplete:fromView:toView:removeFromView:]
    Retain/Autorelease (2)  +1      00:20.663.794   Foundation  hashProbe

Upvotes: 3

Views: 1425

Answers (1)

jbg
jbg

Reputation: 5268

This was my own stupid fault, as I expected, but I’ll answer it here in case it’s useful to anyone!

PPAPI maintains a NSHashTable with weak references for the view subscriptions, which will be automatically zeroed by ARC if the subscribed object is released. It appears that when getting objects from NSHashTable, they are retained and then autoreleased by Foundation.

PPAPI was accessing the NSHashTable within a while(1) loop on a background GCD queue (this is probably bad design). Because the while(1) loop kept the queue busy, the autorelease pool was never drained, with the result that PPLoginViewController was retained by Foundation and never released.

A workaround was putting the content of the while(1) loop in an @autoreleasepool { } block.

The clue to this was the last line of the Allocations report, which shows Retain/Autorelease rather than Retain/Autorelease/Release.

Upvotes: 4

Related Questions