Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSFetchedResultsController keep reference to deallocated delegate controller in storyboard, causing crash

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

like image 712
Leonardo Avatar asked Nov 29 '22 15:11

Leonardo


1 Answers

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.

like image 162
Rob Napier Avatar answered Dec 05 '22 09:12

Rob Napier