NSFetchedResultsController 不改变实体更新的排序顺序;更新但不移动

cha*_*bag 5 core-data nssortdescriptor nsfetchedresultscontroller ios

我的 iOS 应用程序有一个视图控制器,用于显示收件箱项目列表。NSFetchedResultsController这些收件箱项目是核心数据数据库中的实体,并且通过使用主线程管理上下文( )在视图控制器中管理列表NSMainQueueConcurrencyType

除了 之外NSFetchedResultsController,对实体的所有访问都仅限于在具有私有上下文的专用 GCD 调度队列上运行的代码。因此,在实体中设置属性值以及读取这些实体属性值都发生在这个专用的 GCD 队列和上下文上。

视图控制器监视NSFetchedResultsController主线程上下文中的变化。更改发生在后台专用队列和上下文上。代码监视NSManagedObjectContextDidSaveNotification通知,当后台线程更新属性值时,这些更改将被推送到主线程上下文中。

NSFetchedResultsController 设置为对两个排序描述符进行排序,“sortScore”和“date”(现在只有其中一个正在更新,“date”,因此每个托管对象实例都具有相同的“sortScore”,不是吗?影响排序。)

当“日期”发生更改时,会NSFetchedResultsController注意到它并发布更改,但它将其发布为“更新”而不是“移动”,因此排序不会根据排序描述符和“ date”的更新方式使得新值应该落在排序中的其他位置。

经过几个小时的工作和跟踪NSManagedObjectContextDidSaveNotification以及所有其他可能发生故障的点之后,我对为什么获取的结果控制器发送“更新”更改而不是“移动”更改感到茫然。

此应用程序使用 Robbie Hanson XMPPFramework 的旧分叉版本来管理核心数据(以防熟悉)。

(使问题更加复杂的是,昨晚它确实起作用了,并且移动是在一次运行中发生的。我删除了该应用程序并从全新安装开始,它开始工作。但后续运行不起作用并删除该应用程序以启动fresh 并没有解决这个问题,因此 start fresh 的效果可能是巧合。)

我使用了日志记录和断点来显示获取的结果控制器正在发送“更新”而不是“移动”。

这是代码。

设置并进行初始设置,NSFetchedResultsController并在视图控制器的 init 内部调用此 create 方法。

@property  (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;

- (NSFetchedResultsController *)createFetchedResultsControllerUsingStorage:(XMPPCoreDataStorage *)storage entityName:(NSString *)entityName
{
    NSManagedObjectContext *context = [storage mainThreadManagedObjectContext];
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];

    NSSortDescriptor *sortDescriptorScore = [[NSSortDescriptor alloc] initWithKey:@"sortScore" ascending:NO];
    NSSortDescriptor *sortDescriptorDate = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptorScore, sortDescriptorDate, nil];
    [fetchRequest setSortDescriptors:sortDescriptors];

    NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];

    [controller setDelegate:self];

    [self updateResultsDataSetForController:controller];

    return controller;
}

- (BOOL)updateResultsDataSetForController:(NSFetchedResultsController *)controller
{
    NSError *error;
    BOOL success = [controller performFetch:&error];

    if (nil != error) {
        NSLog(@"Error fetching inbox items for the inbox view controller.  Error = %@", error);
    }

    return success;
}
Run Code Online (Sandbox Code Playgroud)

处理更改的代码NSFetchedResultsControllerDelegate基本上是从 Apple 文档复制并粘贴的

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


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
    atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{

    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.conversationsTable insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                            withRowAnimation:UITableViewRowAnimationFade];
            break;

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

        default:
            break;
    }
}


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
    atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
    newIndexPath:(NSIndexPath *)newIndexPath
{

    UITableView *tableView = self.conversationsTable;

    switch(type) {

        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                       withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                       withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate: {
            MyAppInboxCell *cell = [tableView cellForRowAtIndexPath:indexPath];
            MyAppXMPPInboxBaseMember *inboxItem = [self.fetchedResultsController objectAtIndexPath:indexPath];

            MyAppConversation *conversation = [inboxItem conversation];
            [cell configureCell:conversation];
            }
            break;


        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                       withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                       withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}
Run Code Online (Sandbox Code Playgroud)