【问题标题】:Core Data Multiple Threads - View Controller Not Updating核心数据多线程 - 视图控制器未更新
【发布时间】:2012-06-26 08:40:33
【问题描述】:

我正在尝试在我正在开发的应用程序中使用 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 的 Core Data Concurrency 指南,并多次在 Google 和 SO 上搜索答案。我相信它在于使用mergeChangesFromContextDidSaveNotification,但我已经尝试通过注册保存通知并调用合并更改的方法在应用程序委托和表视图控制器的许多不同位置执行此操作,但我没有尝试过作品。 Cocoa is my GF's implementation 是我尝试适应的方式之一。

表视图控制器在创建时传递给应用委托的 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:@"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 存储并运行了相同的过程。种子在启动时会正确加载,但种子之后的更改不会立即出现在表格视图中。如果您滚动到应该在加载之前添加行的位置(并且它不存在),即使在导入完成后它也不会出现。但是,滚动并返回并显示添加的行。似乎当数据库为空时,它没有可滚动的内容,因此不会添加任何内容。有了种子,它最终会添加它们,但不是以我看到的核心数据存储使用动画插入的方式。

【问题讨论】:

    标签: iphone ios multithreading core-data nsmanagedobjectcontext


    【解决方案1】:

    只要你在主线程上的上下文对于应用委托和视图控制器是相同的,这只是你执行合并的设计决策。

    合并本身非常简单。

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

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

    【讨论】:

    • 我确实检查了以确保通知和方法正在触发;导入过程调用上下文保存几次。我想我将不得不三重检查委托上下文和表视图上下文是否相同。
    • 好的,所以在应用程序委托中,我分配表视图控制器,将其 managedObjectContext 属性设置为应用程序委托的 (BeerListViewController.managedObjectContext = self.managedObjectContext),并在整个过程中使用表视图控制器的 managedObjectContext (self.managedObjectContext)表视图控制器。我在应用程序委托的 didFinishLaunchingWithOptions 中注册通知,选择器设置为contextDidSave:contextDidSave: 是上面的确切代码,但视图控制器对导入没有响应。但是,数据会在第二次运行时出现。
    • 我在上面添加了更多关于表视图控制器和应用程序委托的托管对象上下文如何共享相同内存地址而后台 MOC 位于不同内存地址的信息。
    • 能否请您检查合并后主线程上的 MOC 中的数据是否可用。然后我们知道我们是否可以将 NSFetchedResultsController 排除在外,专注于 MOC 本身。
    • 是的,我今晚去看看。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-02
    • 2011-02-07
    • 2021-07-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多