NSFetchedResultsController提供表视图,而同一持久存储的后台更新导致死锁

时间:2014-07-30 21:10:33

标签: ios objective-c core-data magicalrecord

还在努力将应用程序从每次使用或显示时下载信息转换为使用CoreData在手机上缓存(由MagicalRecord提供)。这是在iOS 7上

由于我们没有设置数据推送系统,以便在后端某些数据发生变化时自动更新手机的缓存数据,我一直在考虑过去几个月(当我们处理应用程序的其他方面时)如何管理在手机上保留数据的本地副本,以及能够在缓存中获得最新数据。

我意识到只要我每次仍然获取数据:-(我可以使用手机的CoreData支持的数据缓存来显示和使用,并且只需使用获取数据来更新on电话数据库。

所以我一直在将主要数据对象转换为构成完整对象的下载数据,这些主要数据对象是CoreData对象的轻型替代对象。

基本上,应用程序中的每个普通数据对象,而不是内部包含对象的所有属性,只包含底层CoreData对象的objectID,也可能是内部的应用程序特定ID,所有其他属性都是动态的,并且得到了来自CoreData对象并通过(大多数属性是只读的,更新是通过JSON传递的核心数据的批量重写完成的)

像这样:

- (NSString *)amount
{
    __block NSString *result = nil;

    NSManagedObjectContext *localContext = [NSManagedObjectContext MR_newContext];

    [localContext performBlockAndWait:^{
        FinTransaction  *transaction = (FinTransaction *)[localContext existingObjectWithID:[self objectID] error:nil];

        if (nil != transaction)
        {
            result = [transaction.amount stringValue];
        }
    }];

    return result;
}

偶尔会有一个需要设置,看起来像这样:

- (void)setStatus:(MyTransactionStatus)status
{
    [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
        FinTransaction *transaction = (FinTransaction *)[localContext existingObjectWithID:[self objectID] error:nil];

        if (nil != transaction)
        {
            transaction.statusValue = status;
        }

    } completion:^(BOOL success, NSError *error){}];
}

现在,我的问题是我有一个视图控制器,它基本上使用NSFetchedResultsController在表视图中显示来自本地电话的CoreData数据库的存储数据。在发生这种情况的同时,用户可能开始滚动数据,手机会旋转线程以下载数据更新,然后开始使用更新的数据更新CoreData数据存储,此时它在主线程上运行异步GCD回调,让获取的结果控制器重新获取其数据并告诉表视图重新加载。

问题是,如果用户滚动浏览初始提取的结果控制器获取的数据和表视图加载,并且后台线程正在后台更新相同的Core Data对象,则会发生死锁。它不是被提取和重写的完全相同的实体(当发生死锁时),即,不是正在读取和写入对象ID 1,而是使用相同的持久数据存储。

每次访问,读取或写入都发生在MR_saveWithBlockMR_saveWithBlockAndWait(数据的写入/更新)中,视情况而定,并且[localContext performBlock:]或[localContext performBlockAndWait:]可能是合适的。每个单独的读或写都有自己的NSManagedObjectContext。我还没有看到任何存在悬挂的杂散挂起变化的地方,它阻塞和死锁的实际位置并不总是相同,但总是与后台线程使用的同一持久存储中的主线程读取有关。更新数据。

正在创建获取的结果控制器:

_frController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                                    managedObjectContext:[NSManagedObjectContext MR_rootSavingContext]
                                                      sectionNameKeyPath:sectionKeyPath
                                                               cacheName:nil];

然后完成performFetch

如何在表格视图中显示范围数据并使用新数据在后台更新数据存储时,如何才能最好地构建此类操作?

虽然我大部分时间都使用MagicalRecord,但我可以使用MagicalRecord对有或没有(直接CD)的评论,答案等持开放态度。

1 个答案:

答案 0 :(得分:3)

所以我处理这个的方法是看两个托管对象上下文,每个上下文都有自己的持久存储协调器。两个持久性存储协调器都与磁盘上的同一个持久性存储进行通信。

WWDC 2013会议211中详细介绍了这种方法 - “核心数据性能优化和调试”,您可以访问Apple's Developer Site for WWDC 2013

为了在MagicalRecord中使用这种方法,您需要查看即将发布的MagicalRecord 3.0版本,ClassicWithBackgroundCoordinatorSQLiteMagicalRecordStack(是的,该名称需要工作!)。它实现了WWDC会话中概述的方法,尽管您需要知道您的项目需要进行更改以支持MagicalRecord 3,并且它还没有完全发布。

基本上你最终得到的是:

  • 1 x主线程上下文:您可以使用它来填充UI,以及获取的结果控制器等。不要在此上下文中进行更改
  • 1 x私有队列上下文:使用基于块的已保存方法进行所有更改 - 它们会自动汇集此上下文并保存到磁盘。

我希望这是有道理的 - 绝对可以观看WWDC会话 - 他们使用一些很棒的动画图解释为什么这种方法更快(并且不应该像你现在使用的方法那样阻止主线程)。 / p>

如果您需要,我很乐意详细介绍。