我正在尝试在我正在处理的应用程序中使用 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的一个子类。它创建自己的托管对象上下文,以便将主对象上下文与后台进程分开。
- (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,包含所有委托方法。
在第二次运行应用时,表视图控制器会正确显示,因为数据之前已加载到存储中;但是,导入中所做的任何更新都不会刷新。
我已经多次阅读了Apple的核心数据并发指南,并在Google和SO上搜索了很多次答案。我相信它在于使用 mergeChangesFromContextDidSaveNotification,但我尝试通过注册保存通知并调用方法来合并更改,在应用程序委托和表视图控制器的许多不同位置执行此操作,我尝试过的方法都不起作用。可可是我的GF的实现,是我试图适应的方式之一。
表视图控制器在创建时传递到应用委托的托管对象上下文。
我已经在没有多线程的情况下运行了它,并且导入数据存储并显示在表视图控制器中的代码可以工作,但是当然它会在导入数据时冻结UI。
很明显,我在这里做错了什么;任何帮助将不胜感激。
更新我添加了一些 NSLog 语句和断点,以查看两个 managedObjectContext 是否确实指向相同的内存地址,并且它们似乎指向相同的内存地址,而后台 MOC 位于不同的地址。通知代码似乎应该可以工作并更新主 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://beersandears.net/images/missing.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
}
}
最后,viewDidLoad 调用 fetchBeers 方法来实际执行 fetch。
- (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
测试以确保首先进行提取。确实如此,但不是很多(这是在 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存储的种子并运行了相同的过程。种子在启动时会正确加载,但自种子以来所做的更改不会立即显示在表视图中。如果在加载行之前滚动到应添加行的位置(并且该行不存在),则即使在导入完成后,它也不会显示。但是,向后滚动并返回,将显示添加的行。似乎当数据库为空时,它没有什么可滚动的,因此不会添加任何内容。有了种子,它最终会将它们添加到其中,但不是我看到的核心数据存储与动画插入一起工作的方式。
只要主线程上的上下文对于应用程序委托和视图控制器相同,它只是执行合并的设计决策。
合并本身非常简单。
- 注册 NSManagedObjectContextDidSave 通知。
- 启动后台操作。
- 保存在后台线程上。
- 在主线程上的观察器方法中合并上下文。
下面是如何执行合并的示例代码:
// Whatever method you registered as an observer to NSManagedObjectContextDidSave
- (void)contextDidSave:(NSNotification *)notification
{
[self.managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES];
}
请注意,您实际上必须保存在后台线程上才能触发通知。