首页 文章

核心数据 - 中断父上下文的保留周期

提问于
浏览
14

假设我们在Core Data模型中有两个实体:Departments和Employees .
该部门与员工有一对多的关系 .

我有以下ManagedObjectContexts:

  • Root:连接到持久性存储协调器
  • Main:父Root的上下文

当我想创建一个Employee时,我会执行以下操作:

  • 我在主要环境中有一个部门
  • 我在Main上下文中创建了一个Employee
  • 我将部门分配给员工的部门 property
  • 我保存主要上下文
  • 我保存了Root上下文

这将在Main上下文和Root上下文中创建一个保留周期 .

如果我没有子上下文(所有在Root上下文中)这样做,那么我可以通过在Employee上调用 refreshObject:mergeChanges 来打破保留周期 . 在我使用这两个上下文的情况下,我仍然可以使用该方法来打破Main上下文的循环,但是我如何打破Root上下文的循环呢?

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

Update 15/04: NSPrivateQueueConcurrencyType vs NSMainQueueConcurrencyType
保存两个上下文后,我可以使用Department对象在Main上下文中执行 refreshObject:mergeChanges . 正如预期的那样,这将使Department对象重新出错,打破保留周期并在该上下文中取消分配Department和Employee实体 .

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

奇怪的是:当使用NSMainQueueConcurrencyType创建我的Root上下文(所有分配都被重新出现并解除分配)时,这可以正常工作,但是当我的Root上下文是使用NSPrivateQueueConcurrencyType创建时(所有分配都是重新出错,但是 not dealloced) ) .

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

Update 15/04: Part 2
当我使用NSPrivateQueueConcurrencyType执行另一个(无用,因为没有更改)使用Root上下文保存或回滚时,对象似乎被释放 . 我没有't understand why this doesn'与NSMainQueueConcurrencyType的行为相同 .

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

Update 21/04: Getting there
谢谢Jody Hagings的帮助!
我正试图从我的ManagedObject didSave 方法中移出 refreshObject:mergeChanges .

你能解释一下我之间的区别吗:

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

顶部的一个不会释放对象,底部的就是释放 .

3 回答

  • 3

    我查看了你的示例项目 . 感谢张贴 .

    首先,您看到的行为不是错误......至少在核心数据中没有 . 如您所知,关系会导致保留周期,必须手动打破(在此处记录: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,特别是 Headers 为 Track Changes in Other Threads Using Notifications 的部分的最后一段 .

    当你调用 performBlock 时,你给它的块被包装在一个完整的用户事件中 . 但是, performBlockAndWait 不是这种情况 . 因此,私有上下文MOC将这些对象保留在其 registeredObjects 集合中,直到 processPendingChanges 被调用 .

    在您的示例中,如果您在 performBlockAndWait 内调用 processPendingChanges 或将其更改为 performBlock ,则可以看到已释放的对象 . 这些中的任何一个都将确保MOC完成当前用户事件并从 registeredObjects 集合中删除对象 .

    Edit

    响应你的编辑......并不是第一个没有释放对象 . 这就是MOC仍然将对象注册为故障 . 在同一事件中,在保存之后发生了这种情况 . 如果您只是发出no-op阻止 [context performBlock:^{}] 您将看到从MOC中删除的对象 .

    因此,您不必担心它,因为在该MOC的下一个操作中,将清除对象 . 你不应该有一个长期运行的背景MOC,无论如何都不做任何事情,所以这对你来说真的不是什么大问题 .

    通常,您不希望只刷新所有对象 . 但是,如果你想在保存之后删除所有对象,那么在 didSave: 中执行它的原始概念是合理的,因为在保存过程中会发生这种情况 . 但是,这将导致所有上下文中的对象出错(您可能不想要) . 您可能只希望这种严苛的方法用于后台MOC . 您可以在 didSave: 中查看 object.managedObjectContext 但这不是一个好主意 . 更好的是为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];
        }
    }];
    

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

  • 10

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

    当您在托管对象之间 Build 关系时,每个对象都会对与其相关的一个或多个对象保持强引用 . 这可能会导致强大的参考周期 . 为确保引用周期被破坏,当您完成对象时,可以使用托管对象上下文方法refreshObject:mergeChanges:将其转换为错误 .

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

    Regarding memory footprint in general:

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

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

    与上下文关联的撤消管理器保留对任何已更改的托管对象的强引用 . 默认情况下,在OS X中,上下文的撤消管理器保留无限制的撤消/重做堆栈 . 要限制应用程序的内存占用,应确保在适当的时候擦除(使用removeAllActions)上下文的撤消堆栈 . 除非您对上下文的撤消管理器保持强引用,否则它将根据其上下文取消分配 .

    Update #1:

    在尝试使用分配工具和专门编写的代码片段进行测试后,我可以确认根上下文不会释放内存 . 这可能是一个框架错误,也可能是设计意图 . 我发现了一篇描述同一问题的帖子here .

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

    因此似乎没有什么变通方法,但这是否是一个错误,并将在一些即将发布的版本中修复,或者它将保持原样的设计,我不知道 .

  • 1

    保存到根上下文时,唯一一个持有对象强引用的是根上下文本身,因此,如果只是重置它,则将在根上下文中释放对象 .
    你节省流量应该是:

    • 保持主力
    • 保存根
    • 重置根

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

相关问题