使用核心数据同时和可靠(Using Core Data Concurrently and Relia

2019-07-30 06:20发布

我建设我的第一个iOS应用程序,这在理论上应该是非常简单的,但我有困难,使其充分防弹对我有信心将其提交到App Store。

简要地说,在主屏幕上有一个表视图,在选择的行它塞格斯到显示有关用于在主 - 从方式选择的行信息的另一表视图。 底层数据被检索为从web服务每天一次JSON数据,然后在核心数据存储缓存。 先前的那一天的数据被删除,以无限增长停止SQLite数据库文件。 所有数据持久性操作使用核心数据进行的,与NSFetchedResultsController支撑该细节表视图。

我看到的问题是,如果在主和细节屏幕之间快速切换,正在检索,分析和保存,应用程序冻结或崩溃完全几次,而新的数据。 似乎有某种竞争条件,可能是由于核心数据在后台导入数据,而主线程试图执行一个获取,但我猜测。 我有麻烦获取任何有意义的碰撞信息,通常它是深藏在核心数据堆栈SIGSEGV。

下面的表格示出了当细节表视图控制器被加载发生的事件的实际顺序:

Main Thread                          Background Thread
viewDidLoad

                                     Get JSON data (using AFNetworking)

Create child NSManagedObjectContext (MOC)

                                     Parse JSON data
                                     Insert managed objects in child MOC
                                     Save child MOC
                                     Post import completion notification

Receive import completion notification
Save parent MOC
Perform fetch and reload table view

                                     Delete old managed objects in child MOC
                                     Save child MOC
                                     Post deletion completion notification

Receive deletion completion notification
Save parent MOC

一旦当JSON数据已经到达AFNetworking完成块被触发时,嵌套NSManagedObjectContext被创建并传递到“进口商”对象解析JSON数据并保存对象的核心数据存储。 进口商执行使用新performBlock在IOS 5介绍的方法:

NSManagedObjectContext *child = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [child setParentContext:self.managedObjectContext];        
    [child performBlock:^{
        // Create importer instance, passing it the child MOC...
    }];

进口商对象观察其自己的MOC的NSManagedObjectContextDidSaveNotification然后投递其通过详细表视图控制器观察到的其自身的通知。 当此通知被张贴在表视图控制器执行保存在它自己的(父)MOC。

我用的是相同的基本模式与删除旧数据当天的新数据已导入后“删除器”的对象。 这发生之后,非同步的新数据已经被读取的结果控制器和细节表视图已被重新加载取出。

有一件事我没有做的是观察任何合并的通知或锁定任何管理对象上下文或持久性存储协调员。 这是不是我应该做的事? 我有点不确定如何设计这一切都正确以便将不胜感激的任何建议。

Answer 1:

预iOS 5中,我们通常有两个NSManagedObjectContexts :一个主线程,一个后台线程。 后台线程可以加载或删除数据,然后保存。 得到的NSManagedObjectContextDidSaveNotification然后通过(如你在做)主线程。 我们叫mergeChangesFromManagedObjectContextDidSaveNotification:将那些在主线程上下文。 这为我们运作良好。

其中一个重要的方面是, save:在后台线程阻塞 ,直到后mergeChangesFromManagedObjectContextDidSaveNotification:在主线程运行完毕(因为我们称之为mergeChanges ......从监听到该通知)。 这确保了主线程管理对象的情况下看到这些变化。 我不知道你是否需要 ,如果你有一个父子关系要做到这一点,但你在旧的模式做,以避免各种麻烦。

我不知道有两个上下文之间的父子关系的优势。 这似乎从你的描述,最后保存到磁盘发生在主线程,这可能是不理想的性能的原因上。 (特别是如果你可能会删除大量数据,删除在我们的应用程序的主要成本中最后保存到磁盘一直发生。)

什么代码在运行时,控制器出现/消失,这可能会导致核心数据的麻烦? 什么样的堆栈跟踪的是你看到就死机?



Answer 2:

只是一个建筑理念:

随着您所陈述的数据刷新模式(每天一次,删除,添加数据的完整周期),我真的有动机来创建新的持久性存储的每一天(即命名为日历日期),然后在完成通知,有表视图设置与新的存储(和可能是一个新的MOC)相关联的新fetchedresultscontroller,并刷新使用。 然后,应用程序可以(在其他地方,或许也该通知触发)彻底摧毁“旧”的数据存储。 这种技术解耦该应用程序当前正在使用的数据存储的更新处理,而“开关”到新的数据可能被认为是显着多个原子 ,因为发生变更简单地开始指向新的数据,而不是希望你在不一致的状态并不醒目的店,而新的数据被写入(但尚未完成)。

很显然,我已经留下了一些细节,但我倾向于认为, 虽然使用应重新设计,以减少您所遇到的那种崩溃的可能性正在变多的数据。

乐于进一步讨论...



Answer 3:

NSFetchedResultsController已被证明是巨大的删除有点敏感,所以这是我将开始第一次挖掘。

我最初的问题是,如何是再取出和相关删除操作开始的tableview的重载。 是否有机会删除块将节省孩子MOC而NSFetchedResultsController仍获取或没有?

是否有可能,当你从细节视图切换到主,然后返回到详细信息视图会有运行多个并发的后台任务? 或者,您在Web服务中检索所有的数据在一次不仅是相关的特定行?

一种替代方法,使这个更强大的是使用类似于一个模式UIManagedDocument用途:

而不是使用父MOC为主线并发类型, UIManagedDocument实际创建主MOC私人队列,使孩子MOC提供给您的主线程上使用。 这样做的好处是,所有的I / O在后台继续并保存到父MOC不与孩子MOC干扰都直到孩子MOC是明确地了解他们。 这是因为保存承诺从子到父,而不是周围的其他方式的变化。

所以,如果你做了这是私人父队列的删除,这不会在最终NSFetchedResultsController范围的。 而且由于它是旧数据,实际上是首选的方式。

我提供另一种方法是使用三个背景:

主要MOC( NSPrivateQueueConcurrencyType

  • 负责持久性存储和旧数据的删除。

儿童MOC A( NSMainQueueConcurrencyType

  • 负责任何UI相关的和NSFetchedResultsController

儿童MOC B( NSPrivateQueueConcurrencyType ,儿童MOC A的孩子)

  • 负责插入新的数据,完成后提交它以儿童边际机会。


Answer 4:

主要的问题我已经与多线程的核心数据被无意中访问管理对象的线程/队列比它在创造了另一个。

我已经找到了一个很好的调试工具是加NSAsserts检查至您的主托管对象上下文创建仅用于有管理的对象,并在后台背景下创造的那些主上下文中不使用。

这将涉及到继承的NSManagedObjectContext和NSManagedObject:

  • 一伊娃添加到MOC子类,并分配给它上创建它的队列中。
  • 你莫子应检查当前队列相同的MOC的队列属性。

这只是几行代码,但长期可以阻止你做,是很难追查,否则错误。



文章来源: Using Core Data Concurrently and Reliably