假设在核心数据模型中有两个实体:部门和员工。
该部门与员工之间存在一对多关系。
我有以下ManagedObjectContexts:
-根:连接到持久性存储协调器
-主要:父级根的上下文
当我要创建员工时,请执行以下操作:
-我的主要部门有部门
-我在Main上下文中创建了一个Employee
-我将部门分配给员工的部门财产
-我保存主上下文
-我保存了根上下文
这将在Main上下文和Root上下文中创建一个保留周期。
如果我在没有子上下文(都在Root上下文中)的情况下执行此操作,则可以通过在Employee上调用refreshObject:mergeChanges
来打破保留周期。在两种情况下,我仍然可以使用该方法来中断Main上下文上的周期,但是我将如何打破Root上下文上的周期?
旁注:这是描述我的问题的简单示例。在“工具”中,我可以清楚地看到分配数量的增长。在我的应用程序中,我所处的上下文要比一个层次更深,这会导致更大的问题,因为我得到了一个新的实体分配,并且要保存每个上下文的保留周期。
更新15/04:NSPrivateQueueConcurrencyType与NSMainQueueConcurrencyType
保存两个上下文之后,我可以使用Department对象在Main上下文上执行refreshObject:mergeChanges
。正如预期的那样,这将重新破坏Department对象,打破保留周期,并在这种情况下重新分配Department和Employee实体。
下一步是打破根上下文中存在的保留周期(保存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];
}];
}];
顶部的不释放对象,底部的不释放对象。
最佳答案
我看了您的示例项目。发布的荣誉。
首先,您看到的行为不是错误……至少在Core Data中没有。如您所知,关系会导致保留周期,必须手动将其打破(在此处记录:https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/Articles/cdMemory.html)。
您的代码正在didSave:
中执行此操作。也许有更好的地方可以打破周期,但这是另一回事。
请注意,通过查看registeredObjects
属性,您可以轻松查看在MOC中注册了哪些对象。
但是,您的示例将永远不会在根上下文中释放引用,因为从未在该MOC上调用processPendingEvents
。因此,MOC中已注册的对象将永远不会被释放。
核心数据的概念称为“用户事件”。默认情况下,“用户事件”正确包装在主运行循环中。
但是,对于不在主线程上的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,而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];
}
}];
您将看到,这可能会给您您想要的东西...尽管只有您才能确定自己真正想要完成的工作。
关于ios5 - 核心数据-中断父上下文的保留周期,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/15999932/