NSFetchResultsController 显示虚假对象

NSFetchResultsController shows spurious objects

我有一个 UITableView 显示来自核心数据实体的对象,这些对象是通过 NSFetchResultController 从 WebServer 接收的。用户可以修改它们并将它们发送回服务器。她还可以点击一个按钮来从服务器刷新这些对象。

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

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

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

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

如何调试此类问题?我检查了我的 CoreData 存储 not 是否有同一对象的两个实例(通过查看 SQLite 文件内部,以及在 awakeFromInsert 上放置一个符号断点)。我通过查找现有实例的代码进行了跟踪,发现它没问题。

至此,我卡住了,很难想象调试策略。

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

感谢您的帮助。

京东

编辑 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:这是我的上下文的排列方式。我有一个中央单例模型对象,它负责与远程服务器的通信(因此它的 class 名称 SGIServer)。它包含两个上下文:

它们是在启动时创建的,如下所示:

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]);
                }
            }];
        }
    }
}

和:

- (void)asyncSaveContext: (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 performBlock:^{
            NSError *error = nil;
            if ([mainContext save:&error]) {
                if (saveToDisk) {
                    NSManagedObjectContext *privateContext = self.persistentManagedObjectContext;

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

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

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

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

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