开发者

EXC_BAD_ACCESS - How is this code causing a leak? Traced via malloc_history & NSZombieEnabled

开发者 https://www.devze.com 2023-02-17 12:09 出处:网络
UPDATE (at bottom): Added entire viewWillAppear: method implementation to show how the three UIViewControllers are allocated / deallocated into the UIScrollView.

UPDATE (at bottom): Added entire viewWillAppear: method implementation to show how the three UIViewControllers are allocated / deallocated into the UIScrollView.

I have a root ViewController which contains a UIScrollView. This UIScrollView itself contains 3 different UIViewControllers (setup as NSFetchedResultControllerDelegates for separate UITableViews.)

I need to reload the root ViewController's UIScrollView at one point when we come back to the view so I do this at the beginning of:

-(void) viewWillAppear:(BOOL)animated {
 [dataViewScroller.subviews 
                 makeObjectsPerformSelector:@selector(removeFromSuperview)];
 .......
 // and we proceed to re-load the 3 UIViewControllers into the UIScrollView.
 // From testing this all seems to work fine.
 // The **problem** is that that as soon as this method ends - I get an
 // EXC_BAD_ACCESS error described above.
 // If the above line is removed, everythign works fine.
}

Here is the viewDidLoad method for the each of the UIViewControllers loaded into the UIScrollView is as follows:

 - (void)viewDidLoad {
[super viewDidLoad];

self.view.backgroundColor = [UIColor clearColor];
self.expensesTableView.backgroundColor = [UIColor clearColor];

self.theTableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
self.theTableView.rowHeight = 44;

UIView *containerView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 280, 44)] autorelease];
containerView.backgroundColor = [UIColor clearColor]; 

// Setting up and aligning the label in the center of our view.
UILabel *headerLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 20)];   
headerLabel.text = NSLocalizedString(self.headerText, @"");
headerLabel.textColor = [UIColor blackColor];
headerLabel.font = [UIFont boldSystemFontOfSize:16];
headerLabel.backgroundColor = [UIColor clearColor];
headerLabel.center = containerView.center;
headerLabel.textAlignment = UITextAlignmentCenter;

[containerView addSubview:headerLabel];

// HOW IS THIS A LEAK? CAUSES EXC_BAD_ACCESS If Not commented out
//NSLog(@"Test - %@", headerLabel);
//[headerLabel release];

[self.view addSubview:containerView];

NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
    // Update to handle the error appropriately.
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    exit(-1);  // Fail
}

}

The code above has the line headerLabel release] commented out. With this out the program runs fine! If I enable the line [headerLabel release], which 开发者_运维知识库I technically SHOULD have enabled since otherwise it's a LEAK, GDB then reports the following:

 MyAppTest[11110:207] *** -[CFString release]: message sent to deallocated instance 0x5d66b60
(gdb)

This is the output from malloc_history trace using the above memory address:

Identifier:      MyAppTest
Version:         ??? (???)
Code Type:       X86 (Native)
Parent Process:  gdb-i386-apple-darwin [10116]

Date/Time:       2011-03-15 03:14:47.855 -0400
OS Version:      Mac OS X 10.6.6 (10J567)
Report Version:  6

ALLOC 0x5996770-0x599678f [size=32]: thread_a037a540 |start | main | UIApplicationMain | -[UIApplication _run] | CFRunLoopRunInMode | CFRunLoopRunSpecific | __CFRunLoopRun | __CFRunLoopDoSource1 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ | PurpleEventCallback | _UIApplicationHandleEvent | -[UIApplication sendEvent:] | -[UIApplication handleEvent:withNewEvent:] | -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] | -[UIApplication _reportAppLaunchFinished] | CA::Transaction::commit() | CA::Context::commit_transaction(CA::Transaction*) | CALayerLayoutIfNeeded | -[CALayer layoutSublayers] | -[UILayoutContainerView layoutSubviews] | -[UINavigationController _startDeferredTransitionIfNeeded] | -[UINavigationController _startTransition:fromViewController:toViewController:] | -[AccountViewController viewWillAppear:] | -[NSManagedObjectContext executeFetchRequest:error:] | -[NSPersistentStoreCoordinator executeRequest:withContext:error:] | -[NSSQLCore executeRequest:withContext:error:] | -[NSSQLCore objectsForFetchRequest:inContext:] | -[NSSQLCore newRowsForFetchPlan:] | -[NSSQLCore _newRowsForFetchPlan:selectedBy:withArgument:] | -[NSSQLCore _prepareResultsFromResultSet:usingFetchPlan:withMatchingRows:] | CFStringCreateWithCString | __CFStringCreateImmutableFunnel3 | _CFRuntimeCreateInstance | malloc_zone_malloc 

Pulling my hair out because there is absolutely ZERO messages or calls made to headerLabel after I release it. Anyone have any suggestions? please??

---- UPDATE

- (void) viewWillAppear:(BOOL)animated {

[super viewWillAppear:animated];

[dataViewScroller.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];

[self.myTableViewControllers removeAllObjects];
self.myTableViewControllers = nil;

// All database modifcations should dynamically reload when the view reappears

/* RE-OBTAIN CATEGORIES */
NSManagedObjectContext *context = [myAppTestAppDelegate managedObjectContext];

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"MyObjectType" inManagedObjectContext:context];
[fetchRequest setEntity:entity];

NSError *errorInRequest = nil;
NSArray *results = [context executeFetchRequest:fetchRequest error:&errorInRequest];

if(errorInRequest)
{
    NSLog(@"Unresolved error %@, %@", errorInRequest, [errorInRequest userInfo]);
    abort();
}

[fetchRequest release];

kNumberOfPages = [results count];

dataViewPageControl.numberOfPages = kNumberOfPages;
    dataViewPageControl.currentPage = 0;
dataViewScroller.contentSize = CGSizeMake(dataViewScroller.frame.size.width * kNumberOfPages, dataViewScroller.frame.size.height);

// the view controllers are created lazily
// in the meantime, load the array with placeholders which will be replaced on demand
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < kNumberOfPages; i++) {
    [controllers addObject:[NSNull null]];
}

self.myTableViewControllers = controllers;
[controllers release];

int index = 0;

for (NSManagedObject *type in results) {
    [self loadScrollViewWithPage:index withIdentifier:[type valueForKey:@"name"]];
    index++;
}

}

- (void)loadScrollViewWithPage:(int)page withIdentifier:(NSString *)ident {

if (page < 0) return;
if (page >= kNumberOfPages) return;

MyTableListViewController *controller = [myTableViewControllers objectAtIndex:page];

if ((NSNull *)controller == [NSNull null]) {
    controller = [[MyTableListViewController alloc] initWithPageNumber:page withHeader:ident];
    controller.managedObjectContext = [myAppTestAppDelegate managedObjectContext];
    [myTableViewControllers replaceObjectAtIndex:page withObject:controller];
    [controller release];
}
if (nil == controller.view.superview) {
    CGRect frame = dataViewScroller.frame;
    frame.origin.x = frame.size.width * page;
    frame.origin.y = 0;
    controller.view.frame = frame;
    [dataViewScroller addSubview:controller.view];
}
}

So as you can see, they are always loaded dynamically when the Root ViewController loads. This is because the user can go to an Account Settings type admin page and add additional categories to their setup. When returning to the Root View the NEW / OR DELETED categories must be rendered in the scroll view. If they had 3 Cateogories (ie. 3 TableViewControllers) initially and went to the admin page and created a new one, when returning the the UIScrollView should reload with all 4 categories (4 dynamically created TableViewControllers.) Does this make sense?


Are you sure your view controller is not prematurely released?

It looks like the headerLabel is (over)released, but that doesn't originate from your code, since it is retained by containerView, which is retained by self.view, which is retained by your view controller.

So try to modify your view controllers dealloc method as such:

-(void)dealloc
{
     NSLog(@"View controller deallocing");
     // ... whatever you had here
     [super dealloc];
}

and check the console for the log message.


Update (based on comments):

Cocoa does a lot of view magic behind the scenes, so I think I can see where the problem is. The viewWillAppear notification is automatically sent to all view controllers that are about to appear. This means that it will be sent to your root controller, and all three of the viewcontrollers whose views you have just released!. To put it simply, you are deleting parts of a view heirarchy while Cocoa is in the process of sending messages to the views within that heirarchy.

To solve the problem, I would recommend something a little less drastic. You should not need to completely destroy and re-create the three subviews every time your view will appear. Your root viewController should keep a reference to the three viewControllers that it creates, and then simply send them a message to refresh or redisplay their content when the time comes.


Try manually releasing containerView after adding it to self.view, rather than using autorelease?


if this happens once the method ends then it would suggest that the program is trying to access local data that gets deleted once the method exits.


Thanks to @e.James and @mvds for helping trace this down. Based off the malloc history in this post and @e.James initial suggestion to investigate the fetch I managed to drill down deeper and find the culprit issue.

It was the following code which gets called to create and alloc on of the UIViewControllers found in the UIScrollView:

// Load the view nib and initialize the pageNumber ivar.
- (id)initWithPageNumber:(int)page withHeader:(NSString *)header withType:(NSManagedObject *)type {
if (self = [super initWithNibName:@"ExpenseListViewController" bundle:nil]) {

    pageNumber = page;
            // below WAS the offending line.
    // headerText = header;
            self.headerText = header;

}
return self;

}

Now it it's correct and everything works as intended. Basically I was not using the setter method on a retained & synthesized property (headerText=header). Adding the self. fixed everything.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号