views:

483

answers:

1

Hello guys. I'm a newbie to iphone development and I've been struggling with an EXC_BAD_ACCESS error I got a couple of days ago. I'm basically taking the Stanford iphone class independently and I am trying to pass an array of NSManagedObjects to a TableViewController that's supposed to display them. The application launches in the simulator and displays the data in the tableView but it immediately errors out with an EXC_BAD_ACCESS.

I followed instructions here and other places on how to use NSZombieEnabled to identify prematurely released objects but this one comes without any helpful msgs even with NSZombieEnabled. My guess is it must be caused by something trying to access unassigned memory that wasn't released through release/autorelease. Or else it would have been picked up as zombie object like the other errors I have been able to fix. I'm not a c expert but does that mean something like that could happen if I were to declare an object and send it a message without ever instantiating it? I looked through my code to see if I had anything like that and I came up empty.

I have the stack trace in the debugger for this but I'm not sure how to make use of it. I'm a little frustrated because I can't use breakpoints in the code to narrow down the problem any further since it seems to happen after the app has finished loading. I thought the app would just stay idle if there was no possible user interaction. Is it failing at the tail end of the load where I can't easy see it or is jut doing stuff in the background after it's done loading. And I would much appreciate any tips on how to read the stacktrace for this.

I've typed out my stacktrace below (couldn't figure out how to copy it from the debugger)

0 objc_msgSend
1 ??
2 -[NSManagedObject dealloc]
3 -[_PFManagedObjectReferenceQueue _processReferenceQue:]
4 _performRunLoopAction
5 ___CFRunLoopDoObservers
6 CFRunLoopRunSpecific
7 CFRunLoopRunInMode
8 GSEventRunModal
9 GSEventRun
10 UIApplicationMain
11 main

And the two main classes in my program are the top level delegate class and the ViewTableController it calls.

`- (void)applicationDidFinishLaunching:(UIApplication *)application {

self.tabBarController = [[[UITabBarController alloc] init] autorelease];        

UINavigationController *contactsNavigationController = [[self createContactsNavigationController] retain];

//UINavigationController *recentsNavigationController = [[self createRecentsNavigationController:photos] retain];

tabBarController.viewControllers = [[NSArray alloc] initWithObjects: contactsNavigationController, nil];

[contactsNavigationController release];
//[recentsNavigationController release];

[window addSubview:tabBarController.view];
    [window makeKeyAndVisible];

}

-(UINavigationController *)createContactsNavigationController {

UINavigationController *contactsNavigationController = [[UINavigationController alloc] init];

UITabBarItem *contactsTabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem: UITabBarSystemItemContacts tag:0];
contactsNavigationController.tabBarItem=contactsTabBarItem ;
[contactsTabBarItem  release];


PersonListViewController *personListViewController = [[PersonListViewController alloc] initWithStyle:UITableViewStylePlain];    

NSManagedObjectContext *context = [self managedObjectContext];
personListViewController.managedObjectContext=context;

personListViewController.contacts = [self createContacts];
[context release];

personListViewController.title=@"Contacts";

[contactsNavigationController pushViewController:personListViewController animated:false];
return [contactsNavigationController autorelease];

}`

`- (NSArray *)readContacts {

NSString *path = [[NSBundle mainBundle] bundlePath];

NSString *filePath = [path stringByAppendingPathComponent:@"FakeData.plist"];
NSArray *plist = [[NSMutableArray arrayWithContentsOfFile:filePath] retain];

return [plist autorelease];
}

- (NSMutableArray *)createContacts {

NSArray * plist = [[self readContacts] retain
NSMutableArray *contactNames = [[NSMutableArray alloc] init];
NSMutableArray *contacts = [[NSMutableArray alloc] init];
for (NSDictionary *photo in plist) {
    NSString *contactName = [photo objectForKey:@"user"];
    Person *contact = nil;
    if (![contactNames containsObject:contactName]) {
        contact  = (Person *)[NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:managedObjectContext];
        contact.name =contactName;
        NSError *error;
        if (![managedObjectContext save:&error]) {
            NSLog(@"SHIT the save person FAILED!!!  %@",error);
        }

        [contacts addObject:contact];
        [contactNames addObject:contactName];



    } else {
        contact = [contacts objectAtIndex:[contactNames indexOfObject:contactName]];
    }

    [contactName release];

    Photo *image = (Photo *)[NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:managedObjectContext];

    image.imageFile = [photo objectForKey:@"path"];
    image.imageName = [photo objectForKey:@"name"];
    image.owner = contact;

    contact.photos = [NSSet setWithObjects:image,nil];


     NSError *error;
     if (![managedObjectContext save:&error]) {
         NSLog(@"SHIT the save photoFAILED!!!  %@",error);
     }

    [image release];
    [contact release];
}

[plist release];

return [contacts autorelease];
}

I apologize if my code is too crappy to read.

Thanks for the help guys.

+3  A: 

Here is your problem:

NSString *contactName = [photo objectForKey:@"user"];
... a bunch of lines later
[contactName release];

objectForKey: returns an autoreleased object, you shouldn't release it.

Similarly, insertNewObjectForEntityForName:inManagedObjectContext:managedObjectContext returns an autoreleased object, so remove [image release] and [contact release]

Steven Canfield
I don't know what to say. You are a life-saver. So if I understood this right, my [contactName release] didn't throw an exception because I still had a retainCount of 1 from the auto-released object but once the pool was cleared, the auto-release failed because I had already set its retainCount to 0. How can I catch errors like this in the future if I don't where the pool is being cleared or do I just have to get better at managing my memory. Sorry, I come from a Java world and I am a little frustrated. Thanks though. That was great.
First, good work, your explanation in this comment is right.Second, you'll get better at this with some practice. The ruleIf you see alloc or copy, you've got to see a balanced release. Otherwise, it's autoreleased, so you don't have to do anything to it unless you want to keep it (retain).For finding autorelease bugs, one thing that can help in a larger codebase is adding NSAutoreleasePools. For example:NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];// Do some stuff[pool drain];Now you'll crash on the -drain, which will make the thing you are crashing on obvious.
Steven Canfield
Thanks. I've actually seen sample code that does the local pools. I'll give it a shot. Thanks again.