简体   繁体   中英

Error on second save of NSManagedObjectContext (deallocated insance)

I've read several threads dealing with similar issues on here, but I just can't figure out what I am over-releasing. From a detail view controller for a Player object, I push a UITableViewController to select Location objects for that Player:

- (void)selectLocations 
{
    LocationSelectionController *vc = [[LocationSelectionController alloc] initWithStyle:UITableViewStyleGrouped];
    vc.player = player;
    [self.navigationController pushViewController:vc animated:YES];
    [vc release];
}

Here is a look at some details of the LocationSelectionController:

- (void)saveContext {
    NSManagedObjectContext *context = [player managedObjectContext];
    if ([context hasChanges]) {
    NSError *error = nil;
        if (![context save:&error]) { //*** ERROR HERE ***
           // show alert
        }
    }
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // ....

    NSError *error = nil;
    if (![[self fetchedResultsController] performFetch:&error]) {
        // show alert
    }
}

- (NSFetchedResultsController *)fetchedResultsController {
    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }
    NSManagedObjectContext *context = [player managedObjectContext];
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Location" inManagedObjectContext:context];
    [request setEntity:entity];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)];
    NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
    [request setSortDescriptors:sortDescriptors];

    NSFetchedResultsController *aFRC = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
    aFRC.delegate = self;
    self.fetchedResultsController = aFRC;

    [request release];
    [sortDescriptor release];

    return _fetchedResultsController;
}

- (void)viewDidUnload {
    [super viewDidUnload];
    self.fetchedResultsController = nil;
}


- (void)dealloc {
    self.fetchedResultsController = nil;
    [_fetchedResultsController release];
    [player release];
    [super dealloc];
}

The functionality is always perfect the first time I navigate to the LocationSelectionController. I can interact with the Location objects with no problems at all. When I pop the view and return to the detail view of the Player object, again there is perfect functionality. It is only when I push the LocationSelectionController for a second time (even if it is from a different Player object) that there is a crash up attempting to save the context.

*** -[LocationSelectionController controllerWillChangeContent:]: message sent to deallocated instance 0x7026920

I've tried using instruments with NSZombie to find the problem, and it points me to an instance of LocationSelectionController. 仪器屏幕

Your handling of the NSFetchedResultsController is screwed up. First, consider this code:

NSFetchedResultsController *aFRC = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
aFRC.delegate = self;
self.fetchedResultsController = aFRC;

If your fetchedResultsController property is declared assign , this code is more or less correct (although then the semantics are wrong). If the property is declared retain , you're leaking a reference to aFRC here.

But either way, your deallocation is wrong. In dealloc, you have this:

self.fetchedResultsController = nil;
[_fetchedResultsController release];

The first line sets the property (and this presumably the _fetchedResultsController ivar) to nil, so the second line can never release the object. If the property is declared retain , I guess this is an attempt to clean up the leak mentioned above; if it is declared assign , this is evidence of the incorrect semantics mentioned above. And even if this worked "correctly", the version in viewDidUnload doesn't have that extra release so things are still wrong.

Either way, the leaked reference leaves the NSFetchedResultsController alive. Eventually it finds a change, which it tries to send to its delegate. And that, of course, fails because the delegate has long since been deallocated.


Chances are good that you never need to assign the fetchedResultsController from outside the implementation of this class. What you really should do then is something like this:

The property fetchedResultsController should be declared as:

@property (retain, readonly) NSFetchedResultsController *fetchedResultsController;

Your existing fetchedResultsController is fine, except that it should assign the _fetchedResultsController ivar directly instead of trying to assign self.fetchedResultsController .

Then the dealloc and viewDidUnload methods should each release the object something like this:

[_fetchedResultsController release];
_fetchedResultsController = nil;

You could also set _fetchedResultsController.delegate nil before releasing it, to be extra sure, but the documentation doesn't indicate this is necessary as it does for some other classes.

I had a similar problem with a different outcome.

In my case, I had some KVO observers in the controller that did not have corresponding removeObserver:forKeyPath: calls in dealloc.

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