NSFetchedResultsController:在后台线程中获取

bur*_*rki 8 multithreading core-data uiviewcontroller nsfetchedresultscontroller ios

我有或多或少的基本UITableViewController使用NSFetchedResultsController.在UITableViewController被压入navigationController's堆栈.但推送动画并不流畅,因为提取NSFetchedResultsController是在主线程上执行的,因此会阻止UI.

我的问题是:如何NSFetchedResultsController在后台线程中执行获取以保持动画流畅?

NSFetchedResultsController与委托方法是这样的:

- (NSFetchedResultsController *)fetchedResultsController
{
    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"GPGrade" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];

    //Set predicate
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"parent == %@", self.subject];
    [fetchRequest setPredicate:predicate];


    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
    NSArray *sortDescriptors = @[sortDescriptor];

    [fetchRequest setSortDescriptors:sortDescriptors];

    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"SubjectMaster"];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return _fetchedResultsController;
}

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView beginUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{    
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }

}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{    
    UITableView *tableView = self.tableView;

    switch(type) {
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationTop];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];
            break;

        case NSFetchedResultsChangeUpdate:
            //[self configureCell:(GPSubjectOverviewListCell *)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView endUpdates];
}
Run Code Online (Sandbox Code Playgroud)

que*_*ish 20

TL; DR; 没有充分的理由在主队列上使用上下文.

可以使用NSFetchedResultsController在后台获取数据

绝对.NSFetchedResultsController可以与私有队列上下文一起使用.事实上,这样做非常快乐和高效.有一个错误,NSFetchedResultsController当它使用私有队列时阻止使用它的缓存,但缓存不会像在iOS 3.0中那样赢得你.设置一个cacheName零,你会没事的.

1.使用创建上下文NSPrivateQueueConcurrencyType.最好不要用于IO.

2. 使用该上下文创建获取的结果控制器,并将缓存名称设置为nil.

3.performBlock:块中执行初始提取:

 [[[self fetchedResultsController] managedObjectContext] performBlock:^{
    NSError *fetchError = nil;
    if (![self fetchedResultsController] performFetch:&error]){
        /// handle the error. Don't just log it.
    } else {
        // Update the view from the main queue.
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [tableView reloadData];
         }];
    }
 }];
Run Code Online (Sandbox Code Playgroud)

4.现在,所有委托回调都将从上下文的队列中发生.如果您使用它们来更新视图,请通过调度到主队列来执行此操作,如上所示.

5. ...

6. 利润!

你可以在这里阅读更多相关信息.

  • 请编辑您的答案或将其删除。虽然可以按照您的建议进行操作,但它非常不稳定,并且会导致很多人在尝试修复竞争条件和核心数据多线程违规问题时花费大量时间。 (8认同)
  • @quellish接下来,我在一个测试项目中验证了这种方法绝对不是线程安全的:如果在主线程上调用了`reloadData`并且在后台线程中同时修改了私有上下文,则会创建一个可能导致UITableView和NSFetchedResultsController不同步的竞争状态,这表现为UITableView请求不再存在的节或索引路径。(基本上,使私有上下文连续插入/删除对象,然后在主线程中调用`reloadData`。它将中断。) (3认同)
  • 你确定这是线程安全的吗?在某些时候,如果你正在实现一个表视图数据源,你将不得不从主线程访问 `NSFetchedResultsController` 的属性/方法。例如,`[fetchedResultsController objectAtIndexPath:]`、`fetchedResultsController.sections.count` 等。但是如果您的 NSFetchedResultsController 是在后台线程上创建的,并且正在监听对其托管对象上下文的更改,则它可以独立于主线程中发生了什么。 (2认同)
  • @quellish 在您链接的帖子中,您在 `willDisplayCell:forRowAtIndexPath:` 中调用了 `objectAtIndexPath:`,它发生在主线程中。你怎么知道 `objectAtIndexPath:` 会在 `willDisplayCell:forRowAtIndexPath:` 的调用之间始终如一地返回?在我看来,获取的结果控制器可能会以不可预测的方式从您的下方发生变化。考虑:您将 `fetchedResultsController.sections.count` 返回到表视图。然后它会询问您在第一个索引路径中的单元格。但偷偷在后台线程中删除了所有内容。 (2认同)

Chr*_*isH 1

Core Data 的一般规则是每个线程一个托管对象上下文,每个 MOC 一个线程。考虑到这一点,您需要在主线程上执行获取结果控制器的获取,因为这是将与 FRC 的托管对象交互的线程。(请参阅核心数据编程指南 - 与核心数据的并发

如果您遇到动画性能问题,您可能应该寻找方法来确保在推送视图之前或之后执行提取。通常,您会在视图控制器的 中执行提取viewDidLoad:,并且导航控制器在提取完成之前不会推送视图。