如何处理临时NSManagedObject实例?


86

我需要创建NSManagedObject实例,对其进行处理,然后将其丢弃或存储到sqlite db。问题是,我无法创建NSManagedObject未连接到的实例,NSManagedObjectContext这意味着在我决定不需要数据库中的某些对象之后,必须以某种方式进行清理。

为了解决这个问题,我使用相同的协调器创建了一个内存中的存储,并通过使用assignObject:toPersistentStore.Now来将临时对象放置在其中,如何确保这些临时对象不会到达数据,该数据是我从两家商店的共同点是什么?还是我必须为此任务创建单独的上下文?


UPD:

现在,我正在考虑为内存存储创建单独的上下文。如何将对象从一个上下文移动到另一个上下文?仅使用[context insertObject:]?在这种设置下可以正常工作吗?如果我从对象图中插入一个对象,那么整个图是否也会插入到上下文中?


这应该是一个单独的问题,因为您已将此问题标记为已回答。创建一个新的问题,并解释为什么你觉得你需要一个单独的整个核心数据堆栈JUST一个内存存储。我很乐意与您探讨这个问题。
Marcus S. Zarra

UPD部分现在不相关,因为我选择了另一种方法,请参阅我对您答案的最后评论。
fspirit 2010年

Answers:


146

注意:此答案旧。查看评论以获取完整历史记录。此后,我的建议已更改,我不再建议使用未关联的NSManagedObject实例。我当前的建议是使用临时子NSManagedObjectContext实例。

原始答案

最简单的方法是创建NSManagedObject没有关联的实例NSManagedObjectContext

NSEntityDescription *entity = [NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:myMOC];
NSManagedObject *unassociatedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];

然后,当您要保存它时:

[myMOC insertObject:unassociatedObject];
NSError *error = nil;
if (![myMoc save:&error]) {
  //Respond to the error
}

6
如果unassociatedObject具有对其他未关联对象的引用,我应该一个接一个地插入它们,还是myMOC足够聪明来收集所有引用并也将它们插入?
fspirit 2010年

6
它也足够聪明,可以处理这些关系。
Marcus S. Zarra

2
我喜欢这种方法,可让您在决定存储MO之前将其像常规数据对象一样对待,但担心CoreData合同如何“支持” MO,并因此担心其将来的可靠性。苹果在任何地方都提到或使用这种方法吗?因为如果没有,将来的iOS版本可能会更改动态属性以依赖于MOC并打破这种方法。苹果文档尚不明确:他们强调上下文和指定的初始化程序的重要性,但MO文档中有一则提及“如果上下文不为零,则...”表明零可能没问题
大黄2012年

41
我前一阵子使用了这种方法,但是当我在将它们插入到MOC中之前修改这些对象和/或为其创建关系时,开始看到奇怪的行为并崩溃。我与WWDC的一位核心数据工程师进行了交谈,他说,尽管存在用于未关联对象的API,但他强烈建议不要使用它,因为MOC严重依赖于其对象发送的KVO通知。他建议对常规对象使用常规NSObject,因为这样更安全。
AdrianSchönig2013年

7
这似乎不适用于iOS 8,尤其是对于持久的关系。其他人可以确认吗?
Janum Trivedi 2014年

40

iOS5为Mike Weller的答案提供了更简单的选择。而是使用NSManagedObjectContext。它消除了通过NSNotificationCenter蹦床的需要

要创建子上下文:

NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
childContext.parentContext = myMangedObjectContext;

然后使用子上下文创建对象:

NSManagedObject *o = [NSEntityDescription insertNewObjectForEntityForName:@"MyObject" inManagedObjectContext:childContext];

仅在保存子上下文时应用更改。因此,放弃更改只是不保存。

关系仍然存在限制。即您不能在其他上下文中创建与对象的关系。为了解决这个问题,可以使用objectID来从子上下文中获取对象。例如。

NSManagedObjectID *mid = [myManagedObject objectID];
MyManagedObject *mySafeManagedObject = [childContext objectWithID:mid];
object.relationship=mySafeManagedObject;

注意,保存子上下文将更改应用于父上下文。保存父上下文将保留更改。

有关完整说明,请参见wwdc 2012 session 214


1
感谢您提出建议!我编写了一个演示此方法与至少在OSX上使用nil上下文进行比较的演示,该示例在保存时插入nil上下文时失去了它的属性,因此可以正常工作-演示位于github.com/seltzered/CoreDataMagicalRecordTempObjectsDemo
Vivek Gani

moc第三段中的哪一个?是childContext还是myMangedObjectContext
bugloaf 2014年

这是childContext
Railwayparade 2014年

此解决方案比具有nil上下文更好。
Y

由于NSManagedObject已经提供了相关内容NSManagedObjectContext,因此您可以自动选择上下文:NSManagedObject* objectRelatedContextually = [objectWithRelationship.managedObjectContext objectWithID:objectRelated.objectID];然后选择objectWithRelationship.relationship = objectRelatedContextually;
加里

9

实现此类目标的正确方法是使用新的托管对象上下文。使用相同的持久性存储创建托管对象上下文:

NSManagedObjectContext *tempContext = [[[NSManagedObjectContext alloc] init] autorelease];
[tempContext setPersistentStore:[originalContext persistentStore]];

然后,添加新对象,对其进行突变等。

当需要保存时,您需要在tempContext上调用[tempContext save:...],并处理save通知以将其合并到原始上下文中。要丢弃这些对象,只需释放此临时上下文并忽略它即可。

因此,当您保存临时上下文时,所做的更改将保存到存储中,而您只需要将这些更改返回到主上下文中即可:

/* Called when the temp context is saved */
- (void)tempContextSaved:(NSNotification *)notification {
    /* Merge the changes into the original managed object context */
    [originalContext mergeChangesFromContextDidSaveNotification:notification];
}

// Here's where we do the save itself

// Add the notification handler
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(tempContextSaved:)
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:tempContext];

// Save
[tempContext save:NULL];
// Remove the handler again
[[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:NSManagedObjectContextDidSaveNotification
                                              object:tempContext];

这也是您应该处理多线程核心数据操作的方式。每个线程一个上下文。

如果您需要从此临时上下文访问现有对象(以添加关系等),则需要使用对象的ID来获取新的实例,如下所示:

NSManagedObject *objectInOriginalContext = ...;
NSManagedObject *objectInTemporaryContext = [tempContext objectWithID:[objectInOriginalContext objectID]];

如果您尝试NSManagedObject在错误的上下文中使用,则保存时会出现异常。


为此创建第二个上下文非常浪费,因为站起来NSManagedObjectContext在内存和CPU上都很昂贵。我意识到这最初是在某些Apple示例中,但是他们已经更新并更正了这些示例。
Marcus S. Zarra

2
Apple仍在CoreDataBooks示例代码中使用这种技术(创建第二个托管对象上下文)。

1
注意苹果已经更新了CoreDataBooks,确实它仍然使用两个上下文,但是现在第二个上下文是第一个上下文的子级。这种技术是在2011年WWDC讨论(推荐)呈现303(核心数据在iOS中有什么新的),并在这里提到的(有很多,很多,简单的代码更改合并向上)stackoverflow.com/questions/9791469/...
大黄2012年

4
“为此创建第二个上下文非常浪费,因为站起来一个NSManagedObjectContext在内存和CPU上都非常昂贵。” 。不,这不对。持久性存储协调器的依赖关系(托管对象模型和具体存储)不是上下文。上下文是轻量级的。
2014年

3
@quellish同意。苹果在WWDC最近的核心数据性能讨论中表示,创建上下文非常轻巧。
杰西2014年

9

从nil上下文创建临时对象可以正常工作,直到您实际上尝试与上下文!= nil!的对象建立关系为止。

确保您可以。


我对此
查理

8

您所描述的正是aNSManagedObjectContext的作用。

摘自《核心数据编程指南:核心数据基础》

您可以将托管对象上下文视为智能便笺本。从持久性存储中获取对象时,会将临时副本带到暂存器中,在此它们形成对象图(或对象图的集合)。然后,您可以根据需要修改这些对象。但是,除非您实际保存这些更改,否则持久存储将保持不变。

核心数据编程指南:托管对象验证

这也支撑了代表“便签本”的托管对象上下文的想法—通常,您可以将托管对象放到便签本上并对其进行编辑,但最终还是要提交更改或放弃它们,然后再进行编辑。

NSManagedObjectContext被设计为轻量级的。您可以随意创建和丢弃它们-它是持久性存储协调器,而依赖关系是“繁重的”。单个持久性存储协调器可以具有与其关联的许多上下文。在较旧的,过时的线程限制模型下,这意味着在每个上下文上设置相同的持久性存储协调器。今天,这意味着将嵌套上下文连接到与持久性存储协调器关联的根上下文。

创建上下文,在该上下文中创建和修改托管对象。如果要保留它们并传达这些更改,请保存上下文。否则将其丢弃。

尝试创建独立于的托管对象NSManagedObjectContext会带来麻烦。请记住,核心数据最终是对象图的变更跟踪机制。因此,托管对象实际上是托管对象上下文的一部分。上下文遵循其生命周期如果没有上下文,并非所有托管对象功能都将正常运行。


6

根据您使用临时对象的不同,上述建议也有一些警告。我的用例是我想创建一个临时对象并将其绑定到视图。当用户选择保存此对象时,我想设置与现有对象的关系并保存。我想这样做是为了避免创建一个临时对象来保存这些值。(是的,我可以等到用户保存后再获取视图内容,但是我将这些视图放在表中,这样做的逻辑不太优雅。)

临时对象的选项是:

1)(首选)在子上下文中创建临时对象。这是行不通的,因为我将对象绑定到UI,并且不能保证在子上下文中调用对象访问器。(我没有找到其他说明的文件,所以我不得不假设。)

2)使用nil对象上下文创建临时对象。这不起作用,并导致数据丢失/损坏。

我的解决方案:我通过使用nil对象上下文创建临时对象来解决此问题,但是当我保存对象而不是将其插入为#2时,我将其所有属性复制到了在主上下文中创建的新对象中。我在我的NSManagedObject子类中创建了一个支持方法,称为cloneInto:,它使我可以轻松地复制任何对象的属性和关系。


这就是我想要的。但是我怀疑您将如何处理关系属性?
玛尼

1

对我来说,马库斯的答案行不通。这对我有用:

NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:myMOC];
NSManagedObject *unassociatedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];

然后,如果我决定保存它:

[myMOC insertObject:unassociatedObjet];
NSError *error = nil;
[myMoc save:&error];
//Check the error!

我们也不要忘记释放它

[unassociatedObject release]

1

我正在为Swift重写此答案,因为所有类似的问题都需要迅速重定向到该问题。

您可以使用以下代码声明没有任何ManagedContext的对象。

let entity = NSEntityDescription.entity(forEntityName: "EntityName", in: myContext)
let unassociatedObject = NSManagedObject.init(entity: entity!, insertInto: nil)

稍后,要保存对象,可以将其插入上下文并保存。

myContext.insert(unassociatedObject)
// Saving the object
do {
    try self.stack.saveContext()
    } catch {
        print("save unsuccessful")
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.