iOS: NSFetchedResultsController, ControllerDidChangeContent executed twice after reordering

人盡茶涼 提交于 2019-12-11 02:55:21

问题


Have started before a similar thread, but I now know where the problem lies, so I'm narrowing it down for you:

I have two view controllers. First one is called MainCategoriesViewController, 2nd one is NetIncomeViewController. Both are child of my CoreDataViewController. In the CoreDataViewController I basically just have implemented all delegate methods of the NSFetchedResultsController delegate class with a property which is set if user changes occur (like reordering) and the controller shouldn't track these changes.

Ok my MainCategoriesViewController fetch request is setup like this and called from viewDidLoad:

- (void)setupFetchedResultsController
{
    self.managedObjectContext = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).managedObjectContext;

    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"MainCategory"];
    request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"position" ascending:YES]];

    self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:request
                                                                       managedObjectContext:self.managedObjectContext
                                                                         sectionNameKeyPath:nil                                                                                  cacheName:@"MainCategoryCache"];
}

The reordering is done like this:

- (void)tableView:(UITableView *)tableView
moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath
      toIndexPath:(NSIndexPath *)destinationIndexPath;
{
    if (self.activeField && [self.activeField isFirstResponder]){
        [self.activeField resignFirstResponder];
    }

    self.suspendAutomaticTrackingOfChangesInManagedObjectContext = YES;

    //Makes only a mutable copy of the array, but NOT the objects (references) within
    NSMutableArray *fetchedResults = [[self.fetchedResultsController fetchedObjects] mutableCopy];

    // Grab the item we're moving
    NSManagedObject *resultToMove = [self.fetchedResultsController objectAtIndexPath:sourceIndexPath];

    // Remove the object we're moving from the array.
    [fetchedResults removeObject:resultToMove];
    // Now re-insert it at the destination.
    [fetchedResults insertObject:resultToMove atIndex:[destinationIndexPath row]];

    // All of the objects are now in their correct order. Update each
    // object's displayOrder field by iterating through the array.
    int i = 1;
    for (MainCategory *fetchedResult in fetchedResults)
    {
        fetchedResult.position = [NSNumber numberWithInt:i++];
    }

    self.suspendAutomaticTrackingOfChangesInManagedObjectContext = NO;
}

Now the reordering is very fast. However if you switch for the first time to the NetIncomeViewController (doing the EXACT same fetch, however with a different cache name) the reordering in the MainCategoriesViewController gets slow. I've tracked it down and I know now why. Because after self.suspendAutomaticTrackingOfChangesInManagedObjectContext is set to NO, all NSFetchedResultsController delegate methods are executed TWICE! The first time they are as fast as before visiting the NetIncomeViewController for the first time, and the 2nd one is very slow.

Why is this now executed twice? Because they fetch from the same entity? Why does this even have an effect? And what can I do against it?


回答1:


(Same Answer from your other question)

Are you running the time profiler in Instruments? If so, that will tell you why it is slow. Time Profiler is your friend.

Second, put a break point in the delegate methods. Look at the stack trace, that will show you what is causing things to fire.

Having said that, you are mutating the objects in your NSFetchedResultsController while you are in a callback method of the NSFetchedResultsController.

That is bad.

The NSFetchedResultsController is sensitive to changes in the data. It doesn't care what you change only that you did change something. As soon as you change something, it fires the delegate methods to notify you of the change.

If you change an object that the NSFetchedResultsController is watching while in the delegate you could easily get into an endless loop. The only reason it is only firing twice is that on your second run through, you are setting the order to what it already was.

I would suggest moving your reordering logic to the -[UITableViewDataSource tableView: moveRowAtIndexPath: toIndexPath:] method.




回答2:


Have to answer now my own question. Got it working with two edits:

1) (As flexaddicted suggested) Change the reorder like this in order that now other delegate methods are triggered:

int i = 1;
for (MainCategory *fetchedResult in fetchedResults)
{
    [fetchedResult setValue:[NSNumber numberWithInt:i++] forKey:@"position"];
}

and make sure my managedObjectContext knows of this:

    // Save
    [((AppDelegate *)[[UIApplication sharedApplication] delegate]) saveContext];
    // re-do the fetch so that the underlying cache of objects will be sorted
    // correctly
    [self.fetchedResultsController performFetch:nil];

2) In my second view controller (which has the absolutely same fetch request as MainCategoriesViewController), I removed all delegate methods and redo the fetch each time I the view is displayed (in viewWillAppear).



来源:https://stackoverflow.com/questions/20935311/ios-nsfetchedresultscontroller-controllerdidchangecontent-executed-twice-after

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!