14

假设我们在核心数据模型中有两个实体:部门和员工。
部门与员工是一对多的关系。

我有以下 ManagedObjectContexts:
- Root:连接到 Persistent Store Coordinator
- Main:与父 Root 的上下文

当我想创建一个员工时,我执行以下操作:
- 我在主上下文中有一个部门 - 我在主上下文中
创建一个员工
- 我将部门分配给员工的部门属性
- 我保存主上下文
- 我保存根上下文

这会在 Main 上下文和 Root 上下文中创建一个保留循环。

如果我在没有子上下文的情况下执行此操作(全部在 Root 上下文中),那么我可以通过调用refreshObject:mergeChangesEmployee 来打破保留循环。在我使用这两个上下文的情况下,我仍然可以使用该方法来打破 Main 上下文的循环,但我将如何打破 Root 上下文的循环?

旁注:这是一个描述我的问题的简单示例。在 Instruments 中,我可以清楚地看到分配的数量在增长。在我的应用程序中,我的上下文比一层更深,导致了更大的问题,因为我得到了一个新的实体分配,每个上下文都有保留周期,我正在保存。

15/04 更新:NSPrivateQueueConcurrencyType 与 NSMainQueueConcurrencyType
保存两个上下文后,我可以refreshObject:mergeChanges使用 Department 对象在 Main 上下文上执行。正如预期的那样,这将重新故障部门对象,打破保留周期并在该上下文中取消分配部门和员工实体。

下一步是打破存在于 Root 上下文中的保留循环(保存 Main 上下文已将实体传播到 Root 上下文)。我可以在这里做同样的技巧,并refreshObject:mergeChanges在带有 Department 对象的 Root 上下文中使用。

奇怪的是:当我的根上下文是用 NSMainQueueConcurrencyType 创建的(所有分配都重新出错并解除分配)时,这工作正常,但是当我的根上下文是用 NSPrivateQueueConcurrencyType 创建时不起作用(所有分配都重新出错,但没有解除分配)。

旁注:根上下文的所有操作都在 performBlock(AndWait) 调用中完成

更新 15/04:第 2 部分
当我使用 NSPrivateQueueConcurrencyType 在根上下文上执行另一个(无用,因为没有更改)保存或回滚时,对象似乎已被释放。我不明白为什么这与 NSMainQueueConcurrencyType 的行为不同。

16/04 更新:演示项目
我创建了一个演示项目:http ://codegazer.com/code/CoreDataTest.zip

21/04 更新:到达那里
谢谢 Jody Hagings 的帮助!
我正在尝试refreshObject:mergeChanges移出我的 ManagedObjectdidSave方法。

您能否向我解释一下两者之间的区别:

[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];
    }];
}];

顶部的不会释放对象,底部的会。

4

3 回答 3

10

我查看了您的示例项目。感谢发帖。

首先,您看到的行为不是错误……至少在 Core Data 中不是。如您所知,关系会导致保留周期,必须手动中断(此处记录:https ://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/Articles/cdMemory.html )。

您的代码在didSave:. 可能有更好的地方可以打破循环,但那是另一回事。

请注意,您可以通过查看registeredObjects属性轻松查看 MOC 中注册了哪些对象。

但是,您的示例将永远不会释放根上下文中的引用,因为processPendingEvents永远不会在该 MOC 上调用。因此,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.managedObjectContextdidSave:但这不是一个好主意。最好为 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];
    }
}];

你会看到这可能会给你你想要的……虽然只有你可以确定你真正想要完成的事情。

于 2013-04-20T18:21:12.433 回答
3

您在上面描述的步骤是您在 Core Data 中执行的常见任务。Apple 在Core Data Programming Guide: Object Lifetime Management中清楚地记录了副作用。

当您在托管对象之间建立关系时,每个对象都维护对与其相关的一个或多个对象的强引用。这可能会导致强参考周期。为确保引用循环被打破,当您完成一个对象时,您可以使用托管对象上下文方法 refreshObject:mergeChanges: 将其变为故障。

对象仅在它们不是故障而是实时实例时才保持对彼此的强引用NSManagedObject。使用嵌套上下文,将对象保存在主上下文中,这些更改应该传播到您的根上下文。但是,除非您在根上下文中获取它们,否则不应创建保留周期。保存完主要上下文后,只需刷新这些对象即可。

关于一般的内存占用:

如果您觉得分配变得失控,您可以尝试构建您的代码,以便您在完成任务时丢弃的单独上下文中执行任务,这会导致大量对象触发错误。

此外,如果您使用的是撤消管理器,

与上下文关联的撤消管理器保持对任何更改的托管对象的强引用。默认情况下,在 OS X 中,上下文的撤消管理器保持无限的撤消/重做堆栈。为了限制应用程序的内存占用,您应该确保在适当的时候清理(使用 removeAllActions)上下文的撤消堆栈。除非您保留对上下文的撤消管理器的强引用,否则它将与上下文一起释放。

更新#1:

在尝试了分配工具和专门为测试这一点而编写的一段代码之后,我可以确认根上下文不会释放内存。这要么是框架错误,要么是设计使然。我在这里找到了一篇描述相同问题的帖子。

[context reset]之后调用[context save:]确实释放了内存。我还注意到,在保存之前,根上下文包含我通过子上下文插入的所有对象[context insertedObjects]。遍历它们并做[context refreshObject:mergeChanges:NO]确实重新错误的对象。

所以似乎没有什么变通方法,但我不知道这是否是一个错误并且将在即将发布的版本中修复,或者它会保持设计不变。

于 2013-04-15T05:55:09.123 回答
1

当保存到根上下文时,唯一持有对对象的强引用的是根上下文本身,因此,如果您只是重置它,对象将在根上下文中被释放。
你保存的流程应该是:
-save main
-save root
-reset root

我没有重置或刷新主上下文中的对象,即使没有发现泄漏或僵尸。在父上下文的保存和重置之后,内存似乎被分配和释放。

于 2013-04-14T20:45:19.757 回答