NSFetchedResultsController在更新后看不到新的插入/删除提取的值

在阅读了几十个类似的问题后,我想从这句话开始:“我DID设置了NSFetchedResultsController的委托,它没有用。”

所以我有一个简单的TableViewController,其单元格用NSFetchedResultsController填充。 这是FRC初始化代码:

@property (strong, nonatomic) NSFetchedResultsController *frc; ... - (NSFetchedResultsController *)frc { if (!_frc) { NSError *error = nil; NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:e_product]; request.sortDescriptors = [NSArray arrayWithObjects: [NSSortDescriptor sortDescriptorWithKey:@"product_group.product_group_name" ascending:YES], [NSSortDescriptor sortDescriptorWithKey:f_product_name ascending:YES], nil]; _frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:[DTGGlobalSettings sharedInstance].moc sectionNameKeyPath:@"product_group.product_group_name" cacheName:nil]; _frc.delegate = self; [_frc performFetch:&error]; if (error) { NSLog(@"Error while fetching products: %@!", error.userInfo); } } return _frc; } 

我还使用一些调试标签实现了NSFetchedResultsController委托方法:

 - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { // The fetch controller is about to start sending change notifications, so prepare the table view for updates. NSLog(@"controllerWillChangeContent"); [self.tableView beginUpdates]; } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { UITableView *tableView = self.tableView; switch(type) { case NSFetchedResultsChangeInsert: NSLog(@"didChangeObject - insert"); [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: NSLog(@"didChangeObject - delete"); [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeUpdate: NSLog(@"didChangeObject - update"); [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; break; case NSFetchedResultsChangeMove: NSLog(@"didChangeObject - move"); [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; } } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { switch(type) { case NSFetchedResultsChangeInsert: NSLog(@"didChangeSection - insert"); [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: NSLog(@"didChangeSection - delete"); [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { // The fetch controller has sent all current change notifications, so tell the table view to process all updates. NSLog(@"controllerDidChangeContent"); [self.tableView endUpdates]; } 

在我的navigationBar中,我有刷新按钮,其function是启动产品目录方法,该方法执行以下操作:1。从远程MySQL服务器获取产品2.如果具有ID的产品不存在 – 创建它并填充所有3.如果存在,则根据获取的值更新所有字段

在第2行和第3行之后,商店被保存。 我对整个应用程序中的所有CoreData操作使用单个NSManagedObjectContext。

当我第一次启动应用程序时,TVC是空的,因为还没有产品被提取。 当我按下Refresh时,将获取新产品并将其插入到ManagedObjectContext中,但NSManagedObjectContext不会看到/听到任何更改:不会调用任何委托方法,因此不会向TVC添加新行。 当我重新启动应用程序时,新产品就位于TVC中,没有任何问题。

如果我在那里有一些产品时再次按下刷新按钮,在短暂的延迟后它们都会消失。 一些调试显示,如果在更新实体的字段(实际值相同)并保存商店后发生。 这次委托方法就像一个魅力以及每个更新和保存的实体控制器:didChangeObject:atIndexPath:forChangeType:使用forChangeType = NSFetchedResultsChangeDelete调用newIndexPath。

问题1:为什么NSFetchedResultsController在没有app重启的情况下看不到插入的实体? 问题2:为什么NSFetchedResultsController将已经获取的实体标记为“不存在”(?)并在存储保存后将其从TVC中删除?

我很感激任何帮助和想法,上周与这个问题作斗争;(

UPD1(对于Jody):这里有一些细节。 我使用自定义类DTGGlobalSettings在应用程序中共享一些变量。 这就是我如何初始化其sharedInstance属性:

 +(DTGGlobalSettings *)sharedInstance { static DTGGlobalSettings *myInstance = nil; if (nil == myInstance) { myInstance = [[[self class] alloc] init]; // set values here // try to acces MOC to init CoreData [myInstance moc]; myInstance.baseURL = @"http://local.app/"; } return myInstance; } 

为了初始化CoreData堆栈,我使用了Apple文档中的一个示例,因为出于某种原因,我在创建新项目时没有看到“使用CoreData”复选框。 我确信我之前在Xcode中看过它,但现在我没有;((Xcode 4.4.1):

 #pragma mark Core Data stack - (NSManagedObjectContext *) moc { if (_moc != nil) { return _moc; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (coordinator != nil) { _moc = [[NSManagedObjectContext alloc] init]; [_moc setPersistentStoreCoordinator: coordinator]; } return _moc; } - (NSManagedObjectModel *)managedObjectModel { if (_managedObjectModel != nil) { return _managedObjectModel; } _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil]; return _managedObjectModel; } - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if (_persistentStoreCoordinator != nil) { return _persistentStoreCoordinator; } NSURL *storeUrl = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; storeUrl = [storeUrl URLByAppendingPathComponent:DOC_NAME]; NSError *error; _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]]; if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) { NSLog(@"Error adding a store to the coordinator: %@, %@", error, error.userInfo); } return _persistentStoreCoordinator; } -(void)saveDataStore { NSError *error; if (![self.moc save:&error]) NSLog(@"Unresolved error %@, %@", error, [error userInfo]); } 

然后,DTGGlobalSettings的’moc’(ManagedObjectContext)属性将在我需要访问CoreData的应用程序中的任何位置使用。

现在到第二部分,更新数据库。 我从远程web服务器获取JSON格式的一些新实体,通过生成的字典并为请求结果中找到的每个实体调用createFromDictionary方法。 这是代码:

 +(Product *)createFromDictionary:(NSDictionary *)entityData inContext:(NSManagedObjectContext *)moc performUpdate:(BOOL)update { Product *result; if (update) { NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:e_product]; request.predicate = [NSPredicate predicateWithFormat:@"%K = %@", f_product_id, [entityData objectForKey:f_product_id]]; result = [[DTGGlobalSettings sharedInstance].moc executeFetchRequest:request error:nil].lastObject; if (!result) { NSLog(@"Error updating Product ID %@! Cannot fetch entity.", [entityData objectForKey:f_product_id]); return nil; } } else { result = [NSEntityDescription insertNewObjectForEntityForName:e_product inManagedObjectContext:[DTGGlobalSettings sharedInstance].moc]; } result.product_id = [[DTGGlobalSettings sharedInstance].numFormatter numberFromString:[entityData objectForKey:f_product_id]]; result.product_image = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[DTGGlobalSettings sharedInstance].baseURL stringByAppendingString:[entityData objectForKey:f_product_image]]]]; result.product_name = [entityData objectForKey:f_product_name]; return result; } 

当我用完所提取的实体时,我调用[[DTGGlobalSettings sharedInstance] saveDataStore](见上文)。 此时,TVC中的现有行(如果有的话)消失了。 如果我添加一些新实体,在保存点没有任何反应,即TVC不更新其行。

实际上,我认为你的第一个预编辑post有所有必要的信息。 我只是没有阅读它(我不喜欢不得不继续向右滚动读取长行代码,有时只是不做它 – 我的坏)。

您的问题似乎与您的FRC排序描述符有关。

FRC不能很好地保持关系排序描述符的更新。

有些人认为它是如何设计的。 我认为这是一个错误。

请参阅此文章更改托管对象属性不会触发NSFetchedResultsController更新我的take 的表视图 。 希望你会看到与你的情况有相似之处。

我只是花时间处理相同的问题并发现以下内容:当sectionNameKeyPath引用可以取nil值的关系字段时,FRC更新在插入对象时不起作用。

这可能与Xcode控制台中的警告有关,该警告表示将为该字段设置为nil对象创建该部分。 我想这部分与FRC内部工作混淆了。