Reputation: 5268
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
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