假设我们在核心数据模型中有两个实体:部门和员工 该部门与员工有一对多的关系。
我有以下ManagedObjectContexts:
- Root:连接到Persistent Store Coordinator
- Main:具有父Root的上下文
当我想创建员工时,我会执行以下操作:
- 我在主要背景下有一个部门
- 我在Main主页中创建了一个Employee
- 我将部门分配给员工的部门财产
- 我保存主要上下文
- 我保存了Root上下文
这会在Main上下文和Root上下文中创建一个保留周期。
如果我没有子上下文(所有在Root上下文中)这样做,那么我可以通过调用Employee上的refreshObject:mergeChanges
来中断保留周期。在我使用这两个上下文的情况下,我仍然可以使用该方法来打破Main上下文的循环,但是我如何打破Root上下文的循环呢?
旁注:这是一个描述我的问题的简单示例。在仪器中,我可以清楚地看到分配数量的增长。在我的应用程序中,我的上下文比一个级别更深,导致更大的问题,因为我得到了一个新的实体分配,每个上下文保留周期我正在保存。
更新15/04:NSPrivateQueueConcurrencyType vs NSMainQueueConcurrencyType
保存两个上下文后,我可以使用Department对象在Main上下文中执行refreshObject:mergeChanges
。正如预期的那样,这将使Department对象重新出错,打破保留周期并在该上下文中取消分配Department和Employee实体。
下一步是打破Root上下文中存在的保留周期(保存Main上下文已将实体传播到Root上下文)。我可以在这里做同样的技巧,并使用Department对象在Root上下文中使用refreshObject:mergeChanges
。
奇怪的是:当使用NSMainQueueConcurrencyType创建我的Root上下文(所有分配都被重新出现并解除分配)时,这很好用,但是当使用NSPrivateQueueConcurrencyType创建我的Root上下文时(所有分配都重新出错,但不解除分配。)
旁注:Root上下文的所有操作都是在performBlock(AndWait)调用中完成的
更新15/04:第2部分
当我使用NSPrivateQueueConcurrencyType执行另一个(无用,因为没有更改)使用Root上下文保存或回滚时,对象似乎被释放。我不明白为什么这与NSMainQueueConcurrencyType的行为不一样。
更新16/04:演示项目
我创建了一个演示项目:http://codegazer.com/code/CoreDataTest.zip
更新21/04:到达那里
谢谢Jody Hagings的帮助!
我正在尝试将refreshObject:mergeChanges
移出我的ManagedObject didSave
方法。
你可以向我解释一下之间的区别:
[rootContext performBlock:^{
[rootContext save:nil];
for (NSManagedObject *mo in rootContext.registeredObjects)
[rootContext refreshObject:mo mergeChanges:NO];
}];
和
[rootContext performBlock:^{
[rootContext save:nil];
[rootContext performBlock:^{
for (NSManagedObject *mo in rootContext.registeredObjects)
[rootContext refreshObject:mo mergeChanges:NO];
}];
}];
顶部的一个不会释放对象,底部的就是。
答案 0 :(得分:10)
我看了你的示例项目。感谢张贴。
首先,您看到的行为不是错误......至少在核心数据中没有。如您所知,关系会导致保留周期,必须手动打破(在此处记录:https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/Articles/cdMemory.html)。
您的代码是在didSave:
中执行此操作的。可能有更好的地方来打破这个循环,但那是另一回事。
请注意,您可以通过查看registeredObjects
属性轻松查看在MOC中注册的对象。
但是,您的示例永远不会在根上下文中释放引用,因为永远不会在该MOC上调用processPendingEvents
。因此,MOC中的注册对象永远不会被释放。
Core Data有一个名为“用户事件”的概念。默认情况下,“用户事件”正确包装在主运行循环中。
但是,对于不在主线程上的MOC,您负责确保正确处理用户事件。请参阅此文档:http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html,特别是标题为Track Changes in Other Threads Using Notifications
。
当你致电performBlock
时,你给它的块被包装在一个完整的用户事件中。但是,performBlockAndWait
不是这种情况。因此,私有上下文MOC会将这些对象保留在registeredObjects
集合中,直到processPendingChanges
被调用。
在您的示例中,如果您在processPendingChanges
内拨打performBlockAndWait
或将其更改为performBlock
,则可以看到已发布的对象。其中任何一个都将确保MOC完成当前用户事件并从registeredObjects
集合中删除对象。
修改强>
响应你的编辑......并不是第一个没有释放对象。这就是MOC仍然将对象注册为故障。在同一事件中,在保存之后发生了。如果您只是发出无操作块[context performBlock:^{}]
,您将看到从MOC中删除的对象。
因此,您不必担心它,因为在该MOC的下一个操作中,将清除对象。你不应该有一个长期运行的背景MOC,无论如何都不做任何事情,所以这对你来说真的不是什么大问题。
通常,您不想只刷新所有对象。但是,如果您想要在保存后删除所有对象,那么在didSave:
中执行此操作的原始概念是合理的,因为在保存过程中会发生这种情况。但是,这将导致所有上下文中的对象出错(您可能不想要)。您可能只希望这种严苛的方法用于后台MOC。您可以在object.managedObjectContext
中查看didSave:
,但这不是一个好主意。更好的是为DidSave通知安装一个处理程序......
id observer = [[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification
object:rootContext
queue:nil
usingBlock:^(NSNotification *note) {
for (NSManagedObject *mo in rootContext.registeredObjects) {
[rootContext refreshObject:mo mergeChanges:NO];
}
}];
你会发现这可能会给你你想要的东西......虽然只有你能确定你真正想要完成的事情。
答案 1 :(得分:3)
您在上面描述的步骤是您在Core Data中执行的常见任务。 Apple在Core Data Programming Guide: Object Lifetime Management中明确记录了副作用。
当您在托管对象,每个对象之间建立关系时 保持对它所对象或对象的强引用 有关。这可能会导致强大的参考周期。为了确保 当你完成一个对象时,参考周期就会被打破 可以使用托管对象上下文方法refreshObject:mergeChanges: 把它变成一个错误。
对象只有在不是故障时才会保持对彼此的强引用,而是实时实例NSManagedObject
。使用嵌套上下文,在主上下文中保存对象时,这些更改应传播到根上下文。但是,除非您在根上下文中获取它们,否则不应创建保留周期 。完成保存主要上下文后,应该只需要刷新这些对象。
关于内存占用情况:
如果您觉得分配失控,您可以尝试构建代码,以便执行任务,导致大量对象触发故障,在单独的上下文中,当您完成分配时丢弃任务。
另外,如果您使用的是撤消管理器,
与上下文关联的撤消管理器保留对其的强引用 任何更改的托管对象。默认情况下,在OS X中上下文的撤消 manager保持无限制的undo / redo堆栈。限制你的 应用程序的内存占用,你应该确保你擦洗 (使用removeAllActions)上下文的撤销堆栈时间和时间 适当。除非你强烈引用上下文的撤消 经理,它的背景已被解除分配。
更新#1:
在尝试使用分配工具和专门编写的代码片段进行测试之后,我可以确认根上下文不会释放内存。这可能是一个框架错误,也可能是设计意图。我找到了一篇描述同一问题的帖子here。
在[context reset]
之后调用[context save:]
确实释放了内存。我还注意到,在保存之前,根上下文包含我通过其[context insertedObjects]
集中的子上下文插入的所有对象。迭代它们并执行[context refreshObject:mergeChanges:NO]
会使对象重新出错。
所以似乎没有什么变通方法,但这是否是一个错误,将在一些即将发布的版本中修复,或者它将保持原样设计,我不知道。
答案 2 :(得分:1)
保存到根上下文时,唯一拥有对象强引用的是根上下文本身,因此,如果只是重置它,则会在根上下文中释放对象。
你节省的流量应该是:
-save主要
- 保存根
- 重置根
我没有在主要上下文中重置或刷新对象,即使没有找到泄漏或僵尸。在保存和重置父上下文之后,似乎已分配和释放内存。