有没有一种方法可以实例化NSManagedObject而不将其插入?

60

我有一个用户界面用于插入交易。一旦用户点击加号,他就会得到屏幕,我想实例化我的核心数据NSManagedObject实体并让用户处理它。然后当用户点击保存按钮时,我将调用保存函数。

所以进入代码:

transaction = (Transaction *)[NSEntityDescription insertNewObjectForEntityForName:@"Transaction" inManagedObjectContext:self.managedObjectContext];
//even if i dont call save: its going to show up on my table
[self.managedObjectContext save:&error]

顺便提一下,我在这个表格上使用了一个NSFetchedResultsController,并且我看到NSFetchedResultsController正在向表格中插入一个section和一个object。

我的想法是,如果有一种方法可以实例化Transaction NSManagedObject,那么我就可以更新它而不保存,直到客户端选择保存为止。

6个回答

37

据Marcus Zarra所说,采用nil上下文方法可以避免创建新上下文的开销。详情请参见此答案,该答案回答了类似问题。

更新

我目前正在使用nil上下文方法,并发现一些可能对其他人有用的内容。要创建一个没有上下文的托管对象,需要使用NSManagedObjectinitWithEntity:insertIntoManagedObjectContext:方法。根据Apple关于此方法的文档:

如果context不是nil,则此方法将调用[context insertObject:self] (这会导致调用awakeFromInsert)。

这里的重点很重要。在创建托管对象时,使用nil上下文将防止调用insertObject:,从而防止调用awakeFromInsert。因此,在使用nil上下文时,任何在awakeFromInsert中完成的对象初始化或默认属性值设置都不会自动发生。

底线:当使用没有上下文的托管对象时,awakeFromInsert不会自动调用,您可能需要额外的代码进行补偿。


嗨,这段时间一直有效,直到我尝试在我的事务和一个类别NSManagedObject之间建立关系时。然后应用程序因此崩溃了。有没有什么解决办法? - Edward Ashak
如果您需要建立关系,我建议采用双上下文方法。正如tc所指出的那样,不同上下文中的对象不应该相互引用。另一方面,您可以将设置这些关系的时间推迟到将新的未关联对象插入主上下文之后。 - James Huddleston

19

这是我如何解决的:

在加载时,当我们知道我们正在处理一项新交易时,我创建了一个脱离上下文的交易。

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Transaction" inManagedObjectContext:self.managedObjectContext];
        transaction = (Transaction *)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];

那么当我要建立关系时,我做了这件事:

if( transaction.managedObjectContext == nil){
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Category" inManagedObjectContext:self.managedObjectContext];
        Category *category = (Category *)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
        category.title = ((Category *)obj).title;
        transaction.category = category;
        [category release];
    }
    else {
        transaction.category = (Category *)obj;
    }

最后保存:

if (transaction.managedObjectContext == nil) {
        [self.managedObjectContext insertObject:transaction.category];
        [self.managedObjectContext insertObject:transaction];
    }
    //NSLog(@"\n saving transaction\n%@", self.transaction);

    NSError *error;
    if (![self.managedObjectContext save:&error]) {
        // Update to handle the error appropriately.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        exit(-1);  // Fail
    }

我在想我是否可以... transaction = (Transaction *)[NSEntityDescription insertNewObjectForEntityForName:@"Transaction" inManagedObjectContext:context]; 然后 transaction.managedObjectContext = nil; 这样做会有问题吗? - yunas

19

使用空的MOC存在一个根本性问题:不同MOC中的对象不应该互相引用 - 当关系的其中一方具有空的MOC时,这也适用。如果你保存会发生什么?(如果应用程序的其他部分保存会发生什么?)

如果你的对象没有关系,那么有很多事情可以做(比如NSCoding)。

您可能可以在NSPredicate中使用-[NSManagedObject isInserted](在插入和成功保存之间,它应该为YES)。或者,您可以使用具有相同行为的瞬态属性(在awakeFromInsert中将其设置为YES,在willSave中将其设置为NO)。如果应用程序的其他部分保存,这两种方法都可能存在问题。

使用第二个MOC是CoreData的“标准”用法;它会自动处理冲突检测和解决。当然,您不希望每次更改时都创建一个新的MOC;如果您不介意UI的某些部分看到其他部分中未保存的更改(MOC之间的通信开销可以忽略不计),则对于慢速“用户线程”的未保存更改可能会模糊地感知到一个MOC。


嗨@tc。我尝试了第一个回答,即insertIntoManagedObjectContext:nil,但是当我想要分配关系时,应用程序崩溃并显示错误:reason:'Illegal attempt to establish a relationship 'category' between objects in different contexts. 所以我猜我的问题是,由于在上下文NSManaged对象和上下文外的托管对象之间建立关系是不合法的,那么解决方案是什么? - Edward Ashak
最终我以相同的方式创建了一个类别实体,但在保存时将两者都添加到上下文中,然后它就正常工作了。 - Edward Ashak
2
我可以证明这个答案的正确性。我刚刚遇到了一个与对象上下文为nil相关的问题。在将对象添加到上下文之前分配给对象的属性值在最终将对象添加到子上下文时不会传播到父上下文中。属性在持久化存储中被存储为nil。当我改变顺序(即在将其插入上下文后分配属性值)时,事情就正常了。故事的寓意是,实例化一个没有上下文的对象不是一个好主意。 - Dia Kharrat

8
您可以使用 -[NSManagedObject initWithEntity:insertIntoManagedObjectContext:] 插入一个带有 nilNSManagedObjectContext。当然,在保存之前,您需要将其分配给上下文(使用 -[NSManageObjectContext insertObject:])。然而,这并不是 Core Data 中预期的模式(但请参见 @mzarra 的答案此处)。存在一些棘手的排序问题(即确保实例在期望拥有上下文之前被分配到上下文中等)。更标准的模式是创建一个新的托管对象上下文,并将您的新对象插入该上下文。当用户保存时,保存上下文,并处理 NSManagedObjectDidSaveNotification 将更改合并到您的“主”上下文中。如果用户取消事务,则只需删除上下文并继续进行业务。

我认为你指的是NSManagedObject,而不是NSManagedObjectContext。无论如何,看起来你不能更改与NSManagedObject相关联的MOC - 你可能将其与-[NSManagedObjectContext assignObject:toPersistentStore:]混淆了。 - tc.

2
一个NSManagedObject可以使用nil作为上下文创建,但如果它必须链接到其他NSManagedObjects,则会导致错误。我的方法是将上下文传递到目标屏幕,并在该屏幕上创建一个NSManagedObject。进行所有更改并链接其他NSManagedObjects。如果用户点击取消按钮,我将删除NSManagedObject并保存上下文。如果用户点击保存按钮,我将更新NSManagedObject中的数据,将其保存到上下文中,并释放屏幕。在源屏幕中,我使用重新加载更新表格。
在目标屏幕中删除NSManagedObject会给Core Data足够的时间来更新文件。这通常足够让您在表格视图中看不到更改。在iPhone日历应用程序中,从保存到显示在表格视图中需要一定的延迟。从UI的角度来看,这可能是一个好事情,因为您的用户将关注刚刚添加的行。希望这可以帮助您。

-2
transaction = (Transaction *)[NSEntityDescription insertNewObjectForEntityForName:@"Transaction" inManagedObjectContext:nil];

如果最后一个参数为nil,则会返回一个未保存到数据库的NSManagedObject。

这将导致失败: ***终止应用程序,因为未捕获的异常'NSInvalidArgumentException',原因是:'+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name...' - c9s

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接