Jon Gauthier
Jon Gauthier

Reputation: 25572

App crashes whenever accessing NSManagedObjects in a certain method

I have a method in a UIViewController of my iPhone application (inside a UINavigationController) that is called whenever a row is selected in the table in the ViewController's view. In this method, I access array of "Dream"'s stored in an instance field dreamsArray, which contains NSManagedObjects from my database. I can access objects from this array in other methods, but it seems that whenever I try to retrieve or modify retrieved objects from this array in this particular method, the program crashes.

Here is how dreamsArray is created:

    dreamsArray = [[NSMutableArray alloc] init];

    [self managedObjectContext];

    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Dream" inManagedObjectContext:managedObjectContext];
    [request setEntity:entity];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"title" ascending:NO];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    [request setSortDescriptors:sortDescriptors];
    [sortDescriptors release]; [sortDescriptor release];

    NSError *error;
    NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
    if ( mutableFetchResults == nil )
        NSLog(@"oh noes! no fetch results DreamsTabController:45");

    dreamsArray = [mutableFetchResults mutableCopy];
    [mutableFetchResults release];
    [request release];

An instance in which querying dreamsArray and its objects works:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellID = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if ( cell == nil )
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID] autorelease];

    Dream *dream = (Dream *)[dreamsArray objectAtIndex:indexPath.row];

    cell.textLabel.text = [dream title];
    cell.detailTextLabel.text = @"foo!";

    [dream release];

    return cell;
}

And the method that has all the problems:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    Dream *dream = (Dream *)[dreamsArray objectAtIndex:indexPath.row];
    // BOOM - crashes right here
    EditDreamController *edit = [[EditDreamController alloc] initWithNibName:@"EditDream" bundle:nil];
    edit.dream = [[NSArray alloc] initWithObjects:dream.dreamContent, nil];
    [navigationController pushViewController:edit animated:YES];
    [dream release];
    [edit release];
}

The app crashes immediately after dreamsArray is queried.

Even calling a simple NSLog(@"%@", dream.title) in this method causes a crash. What could be going wrong here?

Upvotes: 1

Views: 4222

Answers (2)

Wil Shipley
Wil Shipley

Reputation: 9533

The following code is shorter, more efficient, easier to read, and doesn't have the six or so memory leaks of your first block:

NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"Dream" inManagedObjectContext:[self managedObjectContext]]];

static NSArray *sortDescriptors = nil;
if (!sortDescriptors)
    sortDescriptors = [[NSArray alloc] initWithObject:[[[NSSortDescriptor alloc] initWithKey:@"title" ascending:NO] autorelease]];
[request setSortDescriptors:sortDescriptors];

NSError *error = nil;
NSArray *fetchResults = [managedObjectContext executeFetchRequest:request error:&error];
if (!fetchResults)
    NSLog(@"oh noes! no fetch results DreamsTabController:45, error %@", error);
[request release];

dreamsArray = [NSMutableArray arrayWithArray:fetchResults];

This method is rewritten to be smaller and not over-release dream, which results in crashers:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
{
    static NSString *cellID = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID] ? : [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID] autorelease];

    Dream *dream = [dreamsArray objectAtIndex:indexPath.row];
    cell.textLabel.text = dream.title;
    cell.detailTextLabel.text = @"foo!";

    return cell;
}

This method had an over-release of 'dream' and a leak on an NSArray and thus a 'dream' instance, as well.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
{
    EditDreamController *editDreamController = [[EditDreamController alloc] initWithNibName:@"EditDream" bundle:nil];

    Dream *dream = [dreamsArray objectAtIndex:indexPath.row];
    editDreamController.dream = [NSArray arrayWithObjects:dream.dreamContent];

    [navigationController pushViewController:editDreamController animated:YES];
    [editDreamController release];
}

It's not clear whey the instance variable on EditDreamController is singular when it takes an array - it should be 'dreams' if you really can set more than one of them.

-Wil

Upvotes: 27

bbum
bbum

Reputation: 162712

Dream *dream = (Dream *)[dreamsArray objectAtIndex:indexPath.row];

cell.textLabel.text = [dream title];
cell.detailTextLabel.text = @"foo!";

[dream release];

You shouldn't be releasing -dream. The array has a hold of it. Same goes for the -tableView:didSelectRowAtIndexPath: method. Most likely, the object has been released enough times as to be deallocated, leaving behind a dangling reference in the array.

End result?

A crash.

Also, your first bit of code has:

dreamsArray = [[NSMutableArray alloc] init];

[self managedObjectContext];

NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Dream" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"title" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release]; [sortDescriptor release];

NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if ( mutableFetchResults == nil )
    NSLog(@"oh noes! no fetch results DreamsTabController:45");

dreamsArray = [mutableFetchResults mutableCopy];

There are several bits of confused code here.

(1) Why do you set dreamsArray to be an empty mutable array, then reset it to refer to a mutable copy of the results of the fetch request? You are leaking the empty mutable array.

(2) You call [self managedObjectContext], but don't do anything with the return value. Then you use managedObjectContext directly. Just use [self managedObjectContext] everywhere. The overhead is negligible.

(3) You create a retained fetch request and assign it to request, but never release it. Another memory leak.

(4) Why are you copying the mutableFetchResults twice? That doesn't make any sense (and is leading to another leak).

All in all, I would suggest revisiting the documentation on Objective-C memory management.

Upvotes: 8

Related Questions