NSFetchResultsController显示虚假对象

时间:2015-10-28 01:30:42

标签: ios objective-c uitableview core-data nsfetchedresultscontroller

我有一个UITableView,它通过NSFetchResultController显示从Web服务器接收的Core Data实体中的对象。用户可以修改它们并将它们发送回服务器。她还可以点击按钮从服务器刷新这些对象。

每个对象都有一个标识符属性。当我从服务器收到对象JSON时,我会查找具有相同标识符的现有对象。它存在,我更新它。否则我会创建它。

其中一些情况发生在主队列NSManagedObjectContext,其中一些位于子私有队列中。在所有情况下,它都发生在performBlock方法中,并保存子上下文及其父上下文。

这听起来像面包和黄油核心数据模式。现在我的问题:

有时,在服务器刷新后,NSFetchResultController会显示同一对象的两个实例。这两个副本是不同的(它们的指针是不同的)。一个副本完成,另一个副本只设置其属性值,而不是其关系。两者都有相同的NSManagedObjectContext。两者都有相同的标识符。

如何调试此类问题?我检查了我的CoreData存储有两个相同对象的实例(通过查看SQLite文件,并在awakeFromInsert上放置一个符号断点)。我遍历了查找现有实例的代码,它发现它没问题。

此时,我陷入困境,我很难想象调试策略。

我可以提供所有可以想象的细节,但除了显示完整的源代码之外,我不确定最有用的是什么。

感谢您的帮助。

JD

编辑1:这是我的controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:

- (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:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

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

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

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

编辑2:这是我的上下文排列方式。我有一个中央单例模型对象,负责与远程服务器的通信(因此它的类名SGIServer)。它有两种情况:

  • mainManagedObjectContextNSMainQueueConcurrencyType用于所有与UI相关的内容,包括上述NSFetchResultController(即使我在网上看到{{1}可以使用私有上下文)。它与持久性存储无关。这是一个孩子:

  • NSFetchResultControllerpersistentManagedObjectContext,与持久性商店相关联,负责在后台保存到商店:

它们是在发布时创建的,如下所示:

NSPrivateQueueConcurrencyType

需要上下文的代码以两种不同的方式执行,具体取决于是否需要主上下文:

NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator;
self.persistentManagedObjectContext = managedObjectContext;
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
managedObjectContext.parentContext = self.persistentManagedObjectContext;
self.mainManagedObjectContext = managedObjectContext;

NSManagedObjectContext *moc = [server mainManagedObjectContext];

其中NSManagedObjectContext *moc = [server newPrivateContext]; 只是创建一个新的newPrivateContext上下文,主要的一个孩子:

NSPrivateQueueConcurrencyType

最后,我定义了两个- (NSManagedObjectContext *) newPrivateContext { NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; privateContext.parentContext = self.mainManagedObjectContext; return privateContext; } 方法,一个是同步方法,另一个是异步方法:

save

- (void)syncSaveContext: (NSManagedObjectContext *) moc persisting:(BOOL)saveToDisk
{
    NSManagedObjectContext *mainContext = self.mainManagedObjectContext;

    if (moc && moc != mainContext) {
        NSError *error = nil;
        if (![moc save:&error]) {
            NSLog(@"Error saving MOC: %@\n%@",[error localizedDescription], [error userInfo]);
        }
    }

    if (mainContext && [mainContext hasChanges]) {
        [mainContext performBlockAndWait:^{
            NSError *error = nil;
            if (![mainContext save:&error]) {
                NSLog(@"Error saving MOC: %@\n%@",[error localizedDescription], [error userInfo]);
            }
        }];
    }
    if (saveToDisk) {
        NSManagedObjectContext *privateContext = self.persistentManagedObjectContext;

        if (privateContext && [privateContext hasChanges]) {
            [privateContext performBlockAndWait: ^{
                NSError *error = nil;
                if (![privateContext save:&error]) {
                    NSLog(@"Error saving private MOC: %@\n%@",[error localizedDescription], [error userInfo]);
                }
            }];
        }
    }
}

异步的是最常用的,通常在任何用户触发的操作结束时。 当我想确保在继续之前完成保存时,偶尔会使用同步。

1 个答案:

答案 0 :(得分:3)

如果您设置了两种上下文(父级和子级,主队列和私有队列),并且在调用上下文保存之后会发生这种情况,那么您可能会遇到类似于临时对象的问题。泄漏到你的背景。据我所知,这是核心数据中的一个错误

在调用save的地方,尝试在父上下文中调用obtainPermanentIDsForObjects执行块,如下所示:

[self.parentContext performBlockAndWait:^{
            NSError * error = nil;
            [self.parentContext obtainPermanentIDsForObjects:[self.parentContext.insertedObjects allObjects] error:&error];
            [self.parentContext save: &error]
        }];