NSFetchedResultsController refers to the freed delegate controller in the storyboard, resulting in a crash

I have a simple UIViewTable, with drillthrough granularity implemented using the UINavigationController push segue in a storyboard. From time to time, it happens that the table view controller seems to be freed while I am in the detailed view, so I get the famous:

[MyViewController controllerWillChangeContent:]: message sent to deallocated instance 

I explain better, I have an NSOperation queue that loads my data asynchronously and populates the table as soon as it ends. The data will be correctly restored and the table is full. For a detailed view, I click on the cell and pass the NSManagedObjectID to the destination controller in the prepareForSegue method. In a very random way, when I changed the detail view, the selected controller lost its delegate, or, as it seems, the delegate itself, which is the controller, is released. Causing a failure.

The controller of the selected results is declared as a property:

 @property(nonatomic,strong) NSFetchedResultsController *fetchedResultsController; 

Then everything works like this, starting with 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 in a detailed view called segue as follows:

 -(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 obtains the identifier of the object and restores the object, setting the context. Then, when I am in the detail view controller, I can make changes to the entity, and when I return to the navigation hierarchy, I save the context. It is at this point that the application crashes, just in the save context. Not so often, but from time to time. Because the result controller recognizes the change and sends it to a delegate who is already released.

I have little doubt at this point, I use iOS 5 and ARC, so the compiler should have (almost) full control over the release and dealloc methods. And I also use a storyboard with a simple navigation hierarchy, which should guarantee the preservation of entire chains of controllers.

I also run the profiler to analyze memory leaks / zombies, but could not detect anything bad, on the contrary, I was happy that all the object management was in order.

I donโ€™t have much guess at this stage, so please feel free to point out that I might forget to check, or something that you see is wrong in my code.

thanks

+4
source share
1 answer

First, a note about ARC. Although ARC provides automatic nulling of weak pointers, it does not automatically nullify assign pointers. NSFetchResultsController uses the assign property for its delegate (see NSFetchedResultsController.h ):

 @property(nonatomic, assign) id< NSFetchedResultsControllerDelegate > delegate; 

You are still responsible for cleansing yourself before the delegate before releasing him. Usually you do this in dealloc :

 - (void)dealloc { _fetchedResultsController.delegate = nil; } 

You can also get rid of the fetchedResultsController in viewWillDisappear: (including deleting yourself as a delegate). Typically, you do not want sampling requests to remain in place when you are behind the scenes. (If you do this, you should probably control the selection in the model object, and not in the view controller, since the view controller can leave at any time when its view is turned off.)

Your loadData strange in that it creates findAllEntities , but actually uses findAllPlants . Is it a typo or a mistake? If ivar has a separate findAllPlants fetch findAllPlants , this could also be the cause of your problems.

+20
source

Source: https://habr.com/ru/post/1395637/


All Articles