核心数据 - 中断保留父上下文的周期 [英] Core Data - break retain cycle of the parent context

查看:286
本文介绍了核心数据 - 中断保留父上下文的周期的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我们在Core Data模型中有两个实体:部门和员工。

部门与员工有一对多的关系。



我有以下ManagedObjectContexts:

- 根:连接到持久存储协调器

- 主:具有父根的上下文



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

- 我在主上下文中有一个部门

- 我在主上下文中创建一个Employee < br>
- 我将部门分配给员工部门的属性

- 我保存主上下文

- 我保存根上下文



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



一个子上下文(所有在上下文中),然后我可以通过调用 refreshObject:mergeChanges 在Employee上打破保留周期。在我有两个上下文的情况下,我仍然可以使用该方法来打破Main上下文的循环,但是我将如何打破Root上下文的循环?



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



更新15/04:NSPrivateQueueConcurrencyType vs NSMainQueueConcurrencyType

保存两个上下文后,我可以执行 refreshObject:mergeChanges 在主上下文与Department对象。这将如预期的那样重新检查Department对象,打破保留周期,并在该上下文中释放Department和Employee实体。



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



事情是:这工作正常,当我的根上下文创建与NSMainQueueConcurrencyType(所有分配重新faulted和dealloced),但不工作,当我的根上下文创建与NSPrivateQueueConcurrencyType(所有分配重新错误,但

p>

更新15/04:第2部分

当我做另一个(无用,因为没有更改)保存或回滚在NSPrivateQueueConcurrencyType的Root上下文中,对象似乎被释放。我不明白为什么这与NSMainQueueConcurrencyType不一样。



更新16/04:演示项目

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



更新21/04:到达

谢谢Jody Hagings的帮助!

我' m尝试将 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:中执行此操作。



请注意,您可以通过查看<$ p>来轻松查看在MOC中注册的对象是什么,然而,您的示例将永远不会释放根上下文中的引用,因为

> processPendingEvents 从不在该MOC上调用。因此,MOC中的注册对象将永远不会被释放。



Core Data有一个称为用户事件的概念。默认情况下,用户事件被正确地包装在主运行循环中。



然而,对于不在主线程上的MOC,您负责确保用户事件正确处理。请参阅此文档: http://developer.apple.com/library /ios/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html ,特别是使用通知跟踪其他主题中的更改的最后一段



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



在你的例子中,如果你在 performBlockAndWait中调用 processPendingChanges code>或将其更改为 performBlock 。任何一个都将确保MOC完成当前用户事件并从 registeredObjects 集合中删除对象。



编辑



响应您的编辑...不是第一个不会释放对象。这是MOC仍然有对象注册为故障。在同一事件期间之后发生的 。如果你只是发出一个无操作块 [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];
}
}];

你会看到,这可能会给你你想要什么...虽然只有你可以决定什么真的想完成。


Let's say we have two entities in a Core Data model: Departments and Employees.
The Department has a one-to-many relationship to Employees.

I have the following ManagedObjectContexts:
- Root: connected to the Persistent Store Coordinator
- Main: context with parent Root

When I want to create an Employee I do the following:
- I have a Department in the Main context
- I create an Employee in the Main context
- I assign the Department to the Employee's department property
- I save the Main context
- I save the Root context

This creates a retain cycle both in the Main context and in the Root context.

If I did this without a child context (all in the Root context), then I could break the retain cycle by calling refreshObject:mergeChanges on Employee. In my situation with the two contexts I could still use that method to break the cycle on the Main context, but how am I going to break the cycle on the Root context?

Side note: this is a simple example to describe my problem. In Instruments I can clearly see the number of allocations growing. In my app I have contexts that go deeper than one level, causing an even greater problem, because I get a new entity allocation with retain cycle per context I'm saving.

Update 15/04: NSPrivateQueueConcurrencyType vs NSMainQueueConcurrencyType
After saving both contexts I can perform refreshObject:mergeChanges on the Main context with the Department object. This will, as expected, re-fault the Department object, break the retain cycle and deallocate the Department and Employee entities in that context.

The next step is to break the retain cycle that exists in the Root context (saving the Main context has propagated the entities to the Root context). I can do the same trick here and use refreshObject:mergeChanges on the Root context with the Department object.

Weird thing is: this works fine when my Root context is created with NSMainQueueConcurrencyType (all allocations are re-faulted and dealloced), but doesn't work when my Root context is created with NSPrivateQueueConcurrencyType (all allocations are re-faulted, but not dealloced).

Side note: all operations for the Root context are done in a performBlock(AndWait) call

Update 15/04: Part 2
When I do another (useless, because there are no changes) save or rollback on the Root context with NSPrivateQueueConcurrencyType, the objects seem to be deallocated. I don't understand why this doesn't behave the same as NSMainQueueConcurrencyType.

Update 16/04: Demo project
I've created a demo project: http://codegazer.com/code/CoreDataTest.zip

Update 21/04: Getting there
Thank you Jody Hagings for your help!
I'm trying to move the refreshObject:mergeChanges out of my ManagedObject didSave methods.

Could you explain to me the difference between:

[rootContext performBlock:^{
    [rootContext save:nil];
    for (NSManagedObject *mo in rootContext.registeredObjects)
        [rootContext refreshObject:mo mergeChanges:NO];
}];

and

[rootContext performBlock:^{
    [rootContext save:nil];
    [rootContext performBlock:^{
        for (NSManagedObject *mo in rootContext.registeredObjects)
            [rootContext refreshObject:mo mergeChanges:NO];
    }];
}];

The top one doesn't deallocate the objects, the bottom one does.

解决方案

I looked at your sample project. Kudos for posting.

First, the behavior you are seeing is not a bug... at least not in Core Data. As you know, relationships cause retain cycles, that must be broken manually (documented here: https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/Articles/cdMemory.html).

Your code is doing this in didSave:. There may be better places to break the cycle, but that's a different matter.

Note that you can easily see what objects are registered in a MOC by looking at the registeredObjects property.

Your example, however, will never release the references in the root context, because processPendingEvents is never called on that MOC. Thus, the registered objects in the MOC will never be released.

Core Data has a concept called a "User Event." By default, a "User Event" is properly wrapped in the main run loop.

However, for MOCs not on the main thread, you are responsible for making sure user events are properly processed. See this documentation: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html, specifically the last paragraph of the section titled Track Changes in Other Threads Using Notifications.

When you call performBlock the block you give it is wrapped inside a complete user-event. However, this is not the case for performBlockAndWait. Thus, the private-context MOC will keep those objects in its registeredObjects collection until processPendingChanges is called.

In your example, you can see the objects released if you either call processPendingChanges inside the performBlockAndWait or change it to performBlock. Either of these will make sure that the MOC completes the current user-event and removes the objects from the registeredObjects collection.

Edit

In response to your edit... It is not that the first one does not dealloc the objects. It's that the MOC still has the objects registered as faults. That happened after the save, during the same event. If you simply issue a no-op block [context performBlock:^{}] you will see the objects removed from the MOC.

Thus, you don't need to worry about it because on the next operation for that MOC, the objects will be cleared. You should not have a long-running background MOC that is doing nothing anyway, so this really should not be a big deal to you.

In general, you do not want to just refresh all objects. However, if you do you want to remove all objects after being saved, then your original concept, of doing it in didSave: is reasonable, as that happens during the save process. However, that will fault objects in all contexts (which you probably don't want). You probably only want this draconian approach for the background MOC. You could check object.managedObjectContext in the didSave: but that's not a good idea. Better would be to install a handler for the DidSave notification...

id observer = [[NSNotificationCenter defaultCenter]
    addObserverForName:NSManagedObjectContextDidSaveNotification
                object:rootContext
                 queue:nil
            usingBlock:^(NSNotification *note) {
    for (NSManagedObject *mo in rootContext.registeredObjects) {
        [rootContext refreshObject:mo mergeChanges:NO];
    }
}];

You will see that this probably gives you what you want... though only you can determine what you are really trying to accomplish.

这篇关于核心数据 - 中断保留父上下文的周期的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆