【问题标题】:Core Data managed object context design recommendationCore Data 托管对象上下文设计推荐
【发布时间】:2012-10-23 15:56:52
【问题描述】:

我们正在开发一个企业级应用程序,该应用程序将使用 Core Data 存储数以万计的对象,我们在几个方面遇到了问题。


我们的应用程序有几个独立的系统,它们在需要时对数据进行操作。这些系统包括项目的发现、项目的加载、同步和 UI 显示。如果我们正确地设计我们的软件,由于不同的系统修改相同的对象,应该几乎没有合并冲突。每个系统都有自己的操作队列,全部在后台执行。我们希望将所有对象的创建和修改都保留在后台,以最大限度地减少 UI 性能问题,尤其是在初始加速期间,可能会从服务器上的数据创建数千个对象。在这里,我们在各种设计尝试中遇到了几个问题。在这些加速过程中会消耗大量内存,并且所有上下文和子上下文的编排不正确,从而导致死锁和崩溃。 我们尝试了以下设计:

  • 一个根NSPrivateQueueConcurrencyType 托管对象上下文,它有一个子NSMainQueueConcurrencyType 上下文。 UI 获取结果控制器使用此子上下文从中获取结果。从NSMainQueueConcurrencyType 子上下文中,我们创建了一个NSPrivateQueueConcurrencyType 子上下文,我们称之为“savingContext”,每个后台操作都创建了该“savingContext”的一个子上下文,进行了更改,最后完成了我们所说的“深度保存”,递归保存到顶部。我们最初选择这种设计是为了不必处理来自许多不同子上下文的NSManagedObjectContextDidSaveNotification 通知。我们包装了对NSPrivateQueueConcurrencyType 上下文的每次调用,并使用performBlockAndWait: 来访问对象。在功能上,这个设计执行了。所有更改和插入都保存到持久存储中,并且 UI 随更改而更新。至此,引入了两个问题。一个是在加速期间 UI 滞后,因为合并更改通过 NSMainQueueConcurrencyType 子上下文,更重要的是,在加速期间内存使用率非常高。由于无法在上下文中递归调用 reset(因为主 UI 子上下文也在那里)和/或不知道何时调用 refreshObject:mergeChanges:,我们会遇到令人望而却步的 RAM 使用。所以我们走了一条不同的路。
  • 有两个与持久存储协调器链接的顶级上下文,一个NSPrivateQueueConcurrencyType 用于保存子上下文,一个NSMainQueueConcurrencyType 用于UI 显示。 NSMainQueueConcurrencyType 监听来自主 NSPrivateQueueConcurrencyType 上下文的 NSManagedObjectContextDidSaveNotification 通知并将它们合并到主线程中。每个后台操作都会创建一个主NSPrivateQueueConcurrencyType 上下文的子上下文,也具有私有队列并发类型,执行它的工作,递归执行“深度保存”,对当前上下文执行保存,递归调用深度保存对其父级,在当前上下文上调用 reset 并再次保存。这样我们就避免了内存问题,因为创建的对象在保存后会很快释放。但是,通过这种设计,我们遇到了很多问题,例如死锁、NSInternalInconsistencyException 异常和获取的结果控制器不更新 UI,尽管有 NSMainQueueConcurrencyType 上下文的保存通知。这也会导致 UI 中的初始加载时间变慢很多。在之前的设计中,获取结果控制器返回结果的速度非常快,而这会使 UI 阻塞几秒钟,直到视图加载(我们在 viewDidLoad 中初始化获取结果控制器)。

我们尝试了许多中间设计,但它们都围绕着相同的问题,要么非常高的内存使用率,要么获取结果控制器不更新 UI,要么死锁和NSInternalInconsistencyException 异常。


我真的很沮丧。我不能不觉得我们的设计对于一些应该相当简单的东西来说过于复杂了,只是我们缺乏对某些基本原理的理解正在扼杀我们。


那你们有什么建议呢?对于我们的情况,您会推荐什么安排?我们应该如何管理不同线程中的不同上下文?释放插入对象和重置上下文的最佳实践?避免死锁?在这一点上,所有帮助将不胜感激。


我还看到了有关 MagicalRecords 类别的建议。推荐吗?我们已经投入使用 Core Data 类型,使用 MR 迁移会有多困难?

【问题讨论】:

  • 我遇到了类似的问题(架构一):stackoverflow.com/questions/15999932/… - 你最终选择了什么样的架构?有什么建议吗?
  • 我们有一个主上下文和一个父根上下文。但是根上下文仅用于保存,因此我们在每次保存时都将其重置。我们有无数的问题。似乎这些子上下文和父上下文的新功能没有经过深思熟虑,Apple 仍然没有处理许多错误。
  • 我倾向于使用带有子 MOC (MainQueue) 的根 MOC (PrivateQueue) 的架构,并通过在我的一些 ManagedObjects 中的 -didSave 中添加 [self.managedObjectContext refreshObject:self mergeChanges:NO]; 调用来减少内存。这打破了关系之间的保留循环,并允许所有 MOC 解除分配对象。只有具有 NSPrivateQueueConcurrencyType 的 MOC 似乎有一个奇怪的问题,即它们不会立即释放对象,而是在下一次保存/回滚时释放。
  • 这不会只释放已保存的对象(具有hasChangesYES)吗? (当 didSave 被调用时,我不是 100%。)
  • NSManagedObjectContext 只保留有变化的 NSManagedObjects(将其视为动态强/弱)。保存后,将不再有任何更改,所有对象都将被释放,除非您自己保留它们。

标签: ios multithreading core-data nsmanagedobjectcontext


【解决方案1】:

首先,为了管理您的内存,您的第二种架构为您提供了更大的灵活性。

其次,有两种内存需要管理:malloc-ed 内存和常驻 VM 内存。您可以拥有较低的 malloc 内存占用,并且仍然拥有较大的 VM 驻留区域。根据我的经验,这是由于 Core Data 积极地坚持新插入的项目。我通过保存后修剪通知解决了这个问题。

第三,MOC 很便宜。使用它们并扔掉它们。换句话说,尽早并经常释放内存。

第四,在主 MOC 上尽量不做任何基于数据库的操作。是的,这听起来适得其反。我的意思是,你所有的复杂查询真的应该在后台线程上完成,然后将结果传递给主线程,或者在利用现在填充的行缓存时从主线程重做查询。通过这样做,您可以保持 UI 处于活动状态。

第五,在我的多队列应用程序中,我尝试让我的所有保存都真正发生在后台。这使我的主要 MOC 保持快速并与来自网络的数据保持一致。

第六,NSFetchedResultsController 是一个非常有用但专门的控制器。如果您的应用程序将其推到其权限范围之外,它就会开始锁定您的界面。发生这种情况时,我会通过自己监听 -didSave 通知来滚动自己的控制器。

【讨论】:

  • 感谢您的回答!锁仓怎么办?目前,我们的应用程序会不时锁定,原因我们尚不清楚。它通常在通知后调用mergeChangesFromContextDidSaveNotification: 时锁定。多个线程可以同时保存吗?上下文是否应该被锁定?
  • Leo,我尽量减少我的 UI 在主 MOC 中花费的时间,方法是从不保存任何更改。 IE。新数据从后台合并到其中。这使主要的 MOC 保持免费。此外,测试锁定是否是因为您正在更新获取的结果控制器或只是合并 MOC。 (我打赌前者。您可以通过不设置 FRC 委托进行测试。)是的,多个线程可以同时保存。由于 SQLite 是一个单线程存储,因此所有内容都通过 PSC 进行序列化——也可以读取。结果,我通过我的后台 MOC 序列化了我的写入/保存。安德鲁
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多