我有一个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
)。它有两种情况:
mainManagedObjectContext
,NSMainQueueConcurrencyType
用于所有与UI相关的内容,包括上述NSFetchResultController
(即使我在网上看到{{1}可以使用私有上下文)。它与持久性存储无关。这是一个孩子:
NSFetchResultController
,persistentManagedObjectContext
,与持久性商店相关联,负责在后台保存到商店:
它们是在发布时创建的,如下所示:
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]);
}
}];
}
}
}
异步的是最常用的,通常在任何用户触发的操作结束时。 当我想确保在继续之前完成保存时,偶尔会使用同步。
答案 0 :(得分:3)
如果您设置了两种上下文(父级和子级,主队列和私有队列),并且在调用上下文保存之后会发生这种情况,那么您可能会遇到类似于临时对象的问题。泄漏到你的背景。据我所知,这是核心数据中的一个错误
在调用save的地方,尝试在父上下文中调用obtainPermanentIDsForObjects
执行块,如下所示:
[self.parentContext performBlockAndWait:^{
NSError * error = nil;
[self.parentContext obtainPermanentIDsForObjects:[self.parentContext.insertedObjects allObjects] error:&error];
[self.parentContext save: &error]
}];