I have a simple UIViewTable, with a drill-down detail realized with a UINavigationController push segue in a storyboard. It happen from time to time, that the table view controller seems to gets deallocated, while I am in the detail view, therefore I get the famous:
[MyViewController controllerWillChangeContent:]: message sent to deallocated instance
I explain better, I have an NSOperation queue which load my data asynchronously, and fill the table as soon as it just finished. The data are correctly retrieved and the table filled. For the detail view, I am clicking on a cell and passing the NSManagedObjectID to the destination controller in prepareForSegue method. Very randomly when I made a change to the detail view the fetched controller loose its delegate, or as it seems, the delegate itself which is the controller gets deallocated. Causing a crash.
The fetched results controller is declared as a property:
@property(nonatomic,strong) NSFetchedResultsController *fetchedResultsController;
Then this is how everything is working starting from viewDidLoad.
- (void)viewDidLoad {
[super viewDidLoad];
[self loadDataAsynchronously];
}
-(void)loadDataAsynchronously {
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(loadData)
object:nil];
[queue addOperation:operation];
}
-(void)loadData {
NSFetchRequest *findAllEntities = [[NSFetchRequest alloc] init];
[findAllEntities setEntity:ENTITY_DESC];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"created" ascending:YES];
[findAllEntities setSortDescriptors:[NSArray arrayWithObject:sort]];
[findAllEntities setFetchBatchSize:20];
[NSFetchedResultsController deleteCacheWithName:@"MyCache"];
if(self.fetchedResultsController==nil) {
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:findAllPlants
managedObjectContext:MOC
sectionNameKeyPath:nil
cacheName:@"MyCache"];
self.fetchedResultsController.delegate=self;
}
NSError *error=nil;
if (![FRC performFetch:&error]) {
exit(EXIT_FAILURE);
}
[self.dataTableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];
}
this code works, and most of the time also works within the detail view which is called as a segue like this:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
IP2SegueIdentifier segueIdentifier = [IP2Factory segueSolver:[segue identifier]];
MyDestinationViewController *dvc = [segue destinationViewController];
NSIndexPath *indexPath = [TV indexPathForSelectedRow];
dvc.entityID=[[self.fetchedResultsController objectAtIndexPath: indexPath] objectID];
}
and the destination controller correctly get the entity id and reconstruct the object by asking the context. Then, when I am in the detail view controller, I can make change to the entity, and when I go back to the navigation hierarchy I do a context save. It is at this point that the application crashes, just right at context save. Not so often, but from time to time. Because the fetched results controller recognize the change and submitted to its delegate which is already deallocated.
I have few doubts at this point, I am using iOS 5 and ARC so the compiler is supposed to have (almost) full control over release and dealloc methods. And I am also using a storyboard with a simple navigation hierarchy, which should guarantee that the whole previous view controllers chain gets retained.
I also run the profiler for memory leak/zombies analysis, but wasn't able to spot anything wrong, on the contrary I was happy the all the objects management were fine.
I have not many guess at this point, so please feel free to point out something I could have forgotten to check, or something you see wrong in my code.
thanks
First, a note about ARC. While ARC provides auto-zeroing weak
pointers, it does not make assign
pointers auto-zeroing. NSFetchResultsController
uses an assign
property for its delegate (see NSFetchedResultsController.h
):
@property(nonatomic, assign) id< NSFetchedResultsControllerDelegate > delegate;
It is still your responsibility to clear yourself as the delegate before you deallocate. You typically do this in dealloc
:
- (void)dealloc {
_fetchedResultsController.delegate = nil;
}
You may also want to get rid of your fetchedResultsController
in viewWillDisappear:
(including removing yourself as the delegate). Typically you do not want fetch requests to stay around when you are offscreen. (If you do, you probably should manage the fetch in a model object rather than in a view controller, since a view controller can go away anytime its view is offscreen.)
Your loadData
is strange in that it creates a findAllEntities
, but actually uses findAllPlants
. Was this a typo or a bug? If there is a separate findAllPlants
fetch request in an ivar, this could also be a cause of your problems.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With