核心数据多线程 - 查看控制器不更新 [英] Core Data Multiple Threads - View Controller Not Updating

查看:194
本文介绍了核心数据多线程 - 查看控制器不更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在我正在开发的应用程式中使用Core Data;在应用程式委托中,此程式码会从下载的JSON档案中导入资料。

   - (BOOL)application:(UIApplication *)应用程序didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{

//设置应用程序窗口等

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

//从JSON导入数据

}

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



在应用程序的第二次运行时,表视图控制器正确显示,因为数据先前已加载到商店中;



我已阅读了 Core Data Concurrency 指南从Apple多次以及搜索很多Google和SO的答案。我相信它在于使用mergeChangesFromContextDidSaveNotification,但我已经尝试在许多不同的地方在应用程序委托和表视图控制器通过注册保存通知和调用方法合并更改,我没有尝试作品。 可可是我的GF's



表视图控制器在创建时传递给应用程序代理的managedObjectContext 。我已经运行这个没有多线程和代码导入到数据存储和显示在表视图控制器工作,但当然,它冻结UI,而导入数据。



很明显我在这里做错了;任何帮助将非常感激。



更新
我添加了一些NSLog语句和断点,看看两个managedObjectContexts是否确实指向相同的内存地址,看起来它们是,而后台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:@BeerinManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];

NSSortDescriptor * sort = [[NSSortDescriptor alloc]
initWithKey:@beertitleascending: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 {
//获取控制器即将开始发送更改通知,因此请准备表视图以进行更新。
[self.tableView beginUpdates]; (NSFetchedResultsChangeType)类型newIndexPath:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType) 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; (NSFetchedResultsChangeType)sectionInfo inIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)(NSFetchedResultsChangeType)$($)类型{

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 {
//获取控制器已发送所有当前更改通知,所以告诉表视图来处理所有更新。
[self.tableView endUpdates];
}

这里也是changeCell的代码,当cell需要更新时被调用:

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

}

最后,fetchBeers方法被调用viewDidLoad实际执行抓取。

   - (void)fetchBeers {

NSError * error;
if(![[self fetchedResultsController] performFetch:& error]){
//更新以正确处理错误。
NSLog(@未解析的错误%@,%@,错误,[错误userInfo]);
exit(-1); // Fail
}
}

更新3



测试以确保首先进行提取。

  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]成功刷新操作

播种一个SQLlite存储并运行相同的进程。种子在启动时正确加载,但种子后的更改不会立即显示在表视图中。如果滚动到在加载行之前应该添加行的位置(并且它不在那里),即使在导入完成后它也不会出现。但是,滚动离开并返回,并显示添加的行。看来,当数据库为空时,它没有滚动到,因此不添加任何东西。使用种子,它最终添加它们,但不是以我看到的核心数据存储使用动画插入。

解决方案

只要你的上下文在主线程是相同的app代理和视图控制器,它只是一个设计决定你执行合并。



合并本身很简单。


  1. 注册NSManagedObjectContextDidSave通知。

  2. 启动背景操作。

  3. 保存在后台主题中。

  4. 在主线程上将观察者方法中的上下文合并。

这里是如何执行合并的示例代码:

  //无论您注册为观察者的方法NSManagedObjectContextDidSave 
- (void)contextDidSave:(NSNotification *)notification
{
[self.managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification :) withObject:notification waitUntilDone:YES];
}

注意,你实际上要在后台线程中保存通知


I am trying to use Core Data in an app I'm working on; in the app delegate this code kicks off the import of data from a downloaded JSON file.

- (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 is a subclass of NSOperation that runs the import. It creates its own managed object context in order to separate the main one from the background process.

- (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

}

The actual import works and updates the data store; however, the table view controller which uses an NSFetchedResultsController does not update. The table view controller is the NSFetchedResultsControllerDelegate and contains all of the delegate methods.

On the second run of the app, the table view controller displays correctly because the data was previously loaded into the store; any updates made in the import do not refresh, however.

I have read the Core Data Concurrency guidelines from Apple multiple times as well as searched many times on Google and SO for the answer. I believe it lies in using mergeChangesFromContextDidSaveNotification, but I have tried to do this in many different places in both the app delegate and the table view controller by registering for the save notification and calling a method to merge changes, and none of what I have tried works. Cocoa is my GF's implementation is one of the ways I have tried to adapt in order to do this.

The table view controller is passed the app delegate's managedObjectContext when it is created.

I have run this without multithreading and the code to import into the data store and display in the table view controller works, but of course it freezes the UI while importing the data.

It's pretty obvious I'm doing something wrong here; any help will be greatly appreciated.

Update I added some NSLog statements and break points to see if the two managedObjectContexts were indeed pointing to the same memory address and it seems that they are, while the background MOC is at a different address. The notification code seems like it should work and update the main MOC, but thus far it is not.

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>

Update 2 After additional troubleshooting it appears the NSFetchedController delegate methods are not firing. Here's the code for the NSFetchedResultsController and its delegate.

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

Also here is the code for changeCell which is called when a cell needs updating:

- (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"]];
    }

}

Finally the fetchBeers method is called by viewDidLoad to actually perform the 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
    }
}

Update 3

Testing to ensure the fetch occurs first. It does, but not by much (this was run on a 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

Instead of starting with a blank SQLlite store, I seeded a SQLlite store and ran through the same process. The seed loads correctly when it starts up, but the changes since the seed do not appear immediately in the table view. If you scroll to the spot where a row should be added before it's loaded (and it's not there), even after the import finishes it does not appear. However, scroll away and come back and the added row appears. It seems that when the database is empty it has nothing to scroll to and therefore doesn't add anything. With the seed it eventually adds them in but not in the way I've seen the core data stores work with an animated insert.

解决方案

As long as your context on the main thread is the same for app delegate and the view controller, it is just a design decision where you perform the merge.

The merge itself is pretty straightforward.

  1. Register for NSManagedObjectContextDidSave notification.
  2. Launch the background op.
  3. Save on the background thread.
  4. Merge the context in the observer method on main thread.

Here is the sample code how you perform the merge:

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

And note, you actually have to save on the background thread for the notification to fire.

这篇关于核心数据多线程 - 查看控制器不更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆