ios - NSFetchedResultsController 提供 TableView ,同时同一持久存储的后台更新导致死锁

标签 ios objective-c core-data magicalrecord

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

因为我们没有设置数据推送系统来在后端发生某些数据更改时自动更新手机的缓存数据,所以在过去的几个月里我一直在思考(因为我们致力于其他方面的工作)应用程序)如何管理在手机上保留数据的本地副本并能够在缓存中拥有最新数据。

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

所以我一直在将主要数据对象从构成完整对象的下载数据转换为这些主要数据对象是 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){}];
}

现在,我的问题是我有一个 View Controller ,它基本上使用 NSFetchedResultsController 在 TableView 中显示来自本地手机的 CoreData 数据库的存储数据。在这种情况发生的同时,用户可能会开始滚动浏览数据,手机会分出一个线程来下载数据更新,然后开始用更新后的数据更新 CoreData 数据存储,此时它会在主线程上运行异步 GCD 回调,让获取的结果 Controller 重新获取其数据,并通知 TableView 重新加载。

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

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

获取的结果 Controller 是这样创建的:

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

然后 performFetch 完成。

如果我需要在 TableView 中显示范围数据并使用新数据在后台更新数据存储,我如何才能最好地构建这种操作?

虽然我大部分都在使用 MagicalRecord,但无论是否使用(直接 CD)使用 MagicalRecord,我都愿意接受评论、回答等。

最佳答案

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

这种方法在 WWDC 2013 的第 211 节中有一些详细的概述——“核心数据性能优化和调试”,您可以在 Apple's Developer Site for WWDC 2013 上找到它。 .

为了将这种方法与 MagicalRecord 一起使用,您需要考虑使用即将发布的 MagicalRecord 3.0 版本,以及 ClassicWithBackgroundCoordinatorSQLiteMagicalRecordStack(是的,这个名字需要工作!)。它实现了 WWDC session 中概述的方法,但您需要知道您的项目需要进行更改以支持 MagicalRecord 3,而且它还没有完全发布。

基本上你最终得到的是:

  • 1 x 主线程上下文:您使用它来填充您的 UI,以及您的获取结果 Controller 等。永远不要在此上下文中进行更改
  • 1 x 私有(private)队列上下文:使用基于 block 的保存方法进行所有更改——它们会自动通过此上下文汇集并保存到磁盘。

我希望这是有道理的——一定要看 WWDC session ——他们使用一些很棒的动画图来解释为什么这种方法更快(并且不应该像你现在使用的方法那样阻塞主线程)。

如果您需要,我很乐意提供更多详细信息。

关于ios - NSFetchedResultsController 提供 TableView ,同时同一持久存储的后台更新导致死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25047019/

相关文章:

ios - 设置RubyMotion应用以播放背景音频

ios - 在 Swift 4 中升级后未调用 textFieldShouldReturn

ios - 如何在Parse中获取用户的FB ID?我可以访问AuthData吗?

ios - 路径上的数据模型编译失败

ios - 从核心数据添加新实体后重新加载 UITableView

objective-c - 在 Core Data 中存储大量文本

ios - 如何在 Tumblr iPhone 中发布图片或文本

iphone - 使用phonegap和titanium的疑惑

objective-c - 如何在 Plist 中存储十六进制值并再次读入

ios - 在 iOS 中清除缓存目录的最佳做法是什么?