本文介绍了NSFetchedResultsController喂养表视图,而同一持久存储的后台更新导致死锁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

仍在使用转换应用程序从下载信息每次使用或显示它,使用CoreData(由MagicalRecord提供)在手机上缓存。这是在iOS 7上



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



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



所以我已经转换主要数据对象从下载的数据组成一个完整的对象,这些主要数据对象



基本上,应用程序中的每个普通数据对象,而不是包含对象内部的所有属性,只包含objectID的底层CoreData对象,也许内部的应用程序特定的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];
}
}];

返回结果;
}

偶尔有一个需要设置, / p>

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

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

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



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



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



每次访问,读取或写入都发生在 MR_saveWithBlock MR_saveWithBlockAndWait 的数据),以及可能适当的[localContext performBlock:]或[localContext performBlockAndWait:]。每个单独的读或写有它自己的 NSManagedObjectContext 。我还没有看到任何地方有悬浮悬而未决的变化挂起,实际的地方它阻塞和死锁并不总是相同,但总是与主线程读取从相同的永久存储作为后台线程使用到更新数据。



正在创建获取的结果控制器,如下所示:

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

,然后执行 performFetch / p>

如何最好地构造这种操作,我需要在表视图中显示扩展数据,并用新数据更新后台的数据存储?



虽然我使用 MagicalRecord 大部分,我可以打开评论,答案等有或没有)使用MagicalRecord。

解决方案

所以我的处理方式是看看有两个管理对象上下文持久存储协调器。两个持久存储协调器都与磁盘上的同一持久存储器通信。



此方法在WWDC 2013 - 核心数据性能优化和调试的会话211中有详细介绍,您可以在。



顺序要使用MagicalRecord这种方法,你需要看看使用即将到来的MagicalRecord 3.0版本,与 ClassicWithBackgroundCoordinatorSQLiteMagicalRecordStack (是的,该名称需要工作!它实现在WWDC会议概述的方法,虽然你需要知道,您的项目将需要更改支持MagicalRecord 3,并且它还没有完全发布。



基本上你最终得到的是:




  • 1 x主线程上下文:你使用它来填充你的UI,您获取的结果控制器等。不要在此上下文中进行更改

  • 1 x私有队列上下文:



我希望这是有意义的 - 绝对观看WWDC会话 - 他们使用一些伟大的动画图来解释为什么这种方法更快(并且不应该像现在使用的方法那样阻塞主线程)。



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


Still working on converting an app over from downloading information every time it uses or displays it, to caching it on-phone using CoreData (courtesy of MagicalRecord). This is on iOS 7

Because we don't have a data-push system set up to automatically update the phone's cached data whenever some data changes on the backend, I've been thinking over the last many months (as we worked on other aspects of the app) how to manage keeping a local copy of the data on the phone and being able to have the most up to date data in the cache.

I realized that as long as I still fetch the data every time :-( I can use the phone's CoreData backed cache of data to display and use, and just use the fetch of the data to update the on-phone database.

So I have been converting over the main data objects from being downloaded data making up a complete object, to these main data objects being light stand-in objects for CoreData objects.

Basically, each of the normal data objects in the app, instead of containing all the properties of the object internally, contains only the objectIDof the underlying CoreData object and maybe the app specific ID internally, and all other properties are dynamic and gotten from the CoreData object and passed through (most properties are read-only and updates are done through bulk-rewriting of the core data from passed in JSON)

Like this:

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

Occasionally there is one that needs to be set and those look like this:

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

Now, my issue is that I have a view controller that basically uses an NSFetchedResultsController to display stored data from the local phone's CoreData database in a table view. At the same time as this is happening, and the user may start to scroll through the data, the phone spins off a thread to download updates to the data and then starts updating the CoreData data store with the updated data, at which point it then runs an asynchronous GCD call back on the main thread to have the fetched results controller refetch its data and and tells the table view to reload.

The problem is that if a user is scrolling through the initial fetched results controller fetched data and table view load, and the background thread is updating the same Core Data objects in the background, deadlocks occur. It is not the exact same entities being fetched and rewritten (when a deadlock occurs), i.e., not that object ID 1 is being read and written, but that the same persistent data store is being used.

Every access, read or write, happens in a MR_saveWithBlock or MR_saveWithBlockAndWait (writes/updates of data) as the case may be, and a [localContext performBlock:] or [localContext performBlockAndWait:] as may be appropriate. Each separate read or write has its own NSManagedObjectContext. I have not seen any where there are stray pending changes hanging around, and the actual places it blocks and deadlocks is not always the same, but always has to do with the main thread reading from the same persistent store as the background thread is using to update the data.

The fetched results controller is being created like this:

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

and then an performFetch is done.

How can I best structure this sort of action where I need to display the extent data in a table view and update the data store in the background with new data?

While I am using MagicalRecord for most of it, I am open to comments, answers, etc with or without (straight CD) using MagicalRecord.

解决方案

So the way I'd handle this is to look at having two managed object contexts each with its own persistent store coordinator. Both of the persistent store coordinators talk to the same persistent store on disk.

This approach is outlined in some detail in Session 211 from WWDC 2013 — "Core Data Performance Optimization and Debugging", which you can get to on Apple's Developer Site for WWDC 2013.

In order to use this approach with MagicalRecord, you will need to look at using the upcoming MagicalRecord 3.0 release, with the ClassicWithBackgroundCoordinatorSQLiteMagicalRecordStack (yes, that name needs work!). It implements the approach outlined in the WWDC session, although you need to be aware that there will be changes needed to your project to support MagicalRecord 3, and that it's also not quite released yet.

Essentially what you end up with is:

  • 1 x Main Thread Context: You use this to populate your UI, and for your fetched results controllers, etc. Don't ever make changes in this context.
  • 1 x Private Queue Context: Make all of your changes using the block-based saved methods — they automatically funnel through this context and save to disk.

I hope that makes sense — definitely watch the WWDC session — they use some great animated diagrams to explain why this approach is faster (and shouldn't block the main thread as much as the approach you're using now).

I'm happy to go into more detail if you need it.

这篇关于NSFetchedResultsController喂养表视图,而同一持久存储的后台更新导致死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-29 09:11