简体   繁体   中英

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?

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM