如何判断NSManagedObject是否已删除?


70

我有一个NSManagedObject已被删除,并且包含该托管对象的上下文已经保存。据我所知,isDeleted返回YES如果核心数据会问持久存储在接下来的保存操作,以删除对象。但是,由于保存已经发生,所以isDeleted返回NO

判断an包含上下文保存是否NSManagedObject已删除的好方法是什么?

(如果您想知道为什么引用删除的托管对象的对象尚不知道删除的原因,那是因为删除和上下文保存是由后台线程启动的,该后台线程使用进行删除和保存performSelectorOnMainThread:withObject:waitUntilDone:。)

Answers:


93

检查托管对象的上下文似乎可行:

if (managedObject.managedObjectContext == nil) {
    // Assume that the managed object has been deleted.
}

从苹果的文档managedObjectContext...

如果接收者已从其上下文中删除,则此方法可能返回nil。

如果接收方有故障,则调用此方法不会导致其触发。

这些似乎都是好事。

更新:如果您要测试是否objectWithID:已删除专门用于检索的托管对象,请查看Dave Gallagher的答案。他指出,如果你调用objectWithID:使用已删除对象的ID,返回的对象将是它的故障不会有它managedObjectContext设置为零。因此,您不能简单地检查它managedObjectContext以测试它是否已被删除。existingObjectWithID:error:如果可以的话使用。如果不是这样,例如,您的目标是Mac OS 10.5或iOS 2.0,则需要执行其他操作来测试删除。有关详细信息,请参见他的答案


isInserted我所知,还有一种方法可以在NSManagedObject上返回BOOL。在这种情况下使用它可能会更清洁一些。

无论如何,在大多数情况下,此managedObjectContext检查足够且快速!
flypig

1
@de,isInserted在保存对象之前只有YES,然后变为NO。文档没有说明这一点,但是我的测试证明了这一点。
phatmann 2014年

1
在iOS 7上进行测试并删除一个对象,然后将该对象合并到主线程上下文中,并且从主线程上下文中为该对象保存的任何引用的托管对象上下文都不为零。尝试通过ID或任何其他获取属性获取对象将返回nil。
jjxtra 2014年

43

更新:一个改进的答案,基于下面讨论中的詹姆斯·哈德斯顿的想法。

- (BOOL)hasManagedObjectBeenDeleted:(NSManagedObject *)managedObject {
    /*
     Returns YES if |managedObject| has been deleted from the Persistent Store, 
     or NO if it has not.

     NO will be returned for NSManagedObject's who have been marked for deletion
     (e.g. their -isDeleted method returns YES), but have not yet been commited 
     to the Persistent Store. YES will be returned only after a deleted 
     NSManagedObject has been committed to the Persistent Store.

     Rarely, an exception will be thrown if Mac OS X 10.5 is used AND 
     |managedObject| has zero properties defined. If all your NSManagedObject's 
     in the data model have at least one property, this will not be an issue.

     Property == Attributes and Relationships

     Mac OS X 10.4 and earlier are not supported, and will throw an exception.
     */

    NSParameterAssert(managedObject);
    NSManagedObjectContext *moc = [self managedObjectContext];

    // Check for Mac OS X 10.6+
    if ([moc respondsToSelector:@selector(existingObjectWithID:error:)])
    {
        NSManagedObjectID   *objectID           = [managedObject objectID];
        NSManagedObject     *managedObjectClone = [moc existingObjectWithID:objectID error:NULL];

        if (!managedObjectClone)
            return YES;                 // Deleted.
        else
            return NO;                  // Not deleted.
    }

    // Check for Mac OS X 10.5
    else if ([moc respondsToSelector:@selector(countForFetchRequest:error:)])
    {
        // 1) Per Apple, "may" be nil if |managedObject| deleted but not always.
        if (![managedObject managedObjectContext])
            return YES;                 // Deleted.


        // 2) Clone |managedObject|. All Properties will be un-faulted if 
        //    deleted. -objectWithID: always returns an object. Assumed to exist
        //    in the Persistent Store. If it does not exist in the Persistent 
        //    Store, firing a fault on any of its Properties will throw an 
        //    exception (#3).
        NSManagedObjectID *objectID             = [managedObject objectID];
        NSManagedObject   *managedObjectClone   = [moc objectWithID:objectID];


        // 3) Fire fault for a single Property.
        NSEntityDescription *entityDescription  = [managedObjectClone entity];
        NSDictionary        *propertiesByName   = [entityDescription propertiesByName];
        NSArray             *propertyNames      = [propertiesByName allKeys];

        NSAssert1([propertyNames count] != 0, @"Method cannot detect if |managedObject| has been deleted because it has zero Properties defined: %@", managedObject);

        @try
        {
            // If the property throws an exception, |managedObject| was deleted.
            (void)[managedObjectClone valueForKey:[propertyNames objectAtIndex:0]];
            return NO;                  // Not deleted.
        }
        @catch (NSException *exception)
        {
            if ([[exception name] isEqualToString:NSObjectInaccessibleException])
                return YES;             // Deleted.
            else
                [exception raise];      // Unknown exception thrown.
        }
    }

    // Mac OS X 10.4 or earlier is not supported.
    else
    {
        NSAssert(0, @"Unsupported version of Mac OS X detected.");
    }
}

旧的/弃用的答案:

我写了一个更好的方法。self是您的核心数据类/控制器。

- (BOOL)hasManagedObjectBeenDeleted:(NSManagedObject *)managedObject
{
    // 1) Per Apple, "may" be nil if |managedObject| was deleted but not always.
    if (![managedObject managedObjectContext])
        return YES;                 // Deleted.

    // 2) Clone |managedObject|. All Properties will be un-faulted if deleted.
    NSManagedObjectID *objectID             = [managedObject objectID];
    NSManagedObject   *managedObjectClone   = [[self managedObjectContext] objectWithID:objectID];      // Always returns an object. Assumed to exist in the Persistent Store. If it does not exist in the Persistent Store, firing a fault on any of its Properties will throw an exception.

    // 3) Fire faults for Properties. If any throw an exception, it was deleted.
    NSEntityDescription *entityDescription  = [managedObjectClone entity];
    NSDictionary        *propertiesByName   = [entityDescription propertiesByName];
    NSArray             *propertyNames      = [propertiesByName allKeys];

    @try
    {
        for (id propertyName in propertyNames)
            (void)[managedObjectClone valueForKey:propertyName];
        return NO;                  // Not deleted.
    }
    @catch (NSException *exception)
    {
        if ([[exception name] isEqualToString:NSObjectInaccessibleException])
            return YES;             // Deleted.
        else
            [exception raise];      // Unknown exception thrown. Handle elsewhere.
    }
}

正如詹姆斯·哈德斯顿(James Huddleston)在回答中提到的那样,检查一下NSManagedObject的-managedObjectContext退货nil是否是一种“相当不错的”方式,以查看是否已从持久性存储中删除了已缓存/过时的NSManagedObject,但这并不总是如Apple在其文档中所指出的那样准确:

这个方法 如果接收者已从其上下文中删除,则可能返回nil。

什么时候不返回nil?如果您使用已删除的NSManagedObject的商品获得其他NSManagedObject的商品-objectID如下所示:

// 1) Create a new NSManagedObject, save it to the Persistant Store.
CoreData        *coreData = ...;
NSManagedObject *apple    = [coreData addManagedObject:@"Apple"];

[apple setValue:@"Mcintosh" forKey:@"name"];
[coreData saveMOCToPersistentStore];


// 2) The `apple` will not be deleted.
NSManagedObjectContext *moc = [apple managedObjectContext];

if (!moc)
    NSLog(@"2 - Deleted.");
else
    NSLog(@"2 - Not deleted.");   // This prints. The `apple` has just been created.



// 3) Mark the `apple` for deletion in the MOC.
[[coreData managedObjectContext] deleteObject:apple];

moc = [apple managedObjectContext];

if (!moc)
    NSLog(@"3 - Deleted.");
else
    NSLog(@"3 - Not deleted.");   // This prints. The `apple` has not been saved to the Persistent Store yet, so it will still have a -managedObjectContext.


// 4) Now tell the MOC to delete the `apple` from the Persistent Store.
[coreData saveMOCToPersistentStore];

moc = [apple managedObjectContext];

if (!moc)
    NSLog(@"4 - Deleted.");       // This prints. -managedObjectContext returns nil.
else
    NSLog(@"4 - Not deleted.");


// 5) What if we do this? Will the new apple have a nil managedObjectContext or not?
NSManagedObjectID *deletedAppleObjectID = [apple objectID];
NSManagedObject   *appleClone           = [[coreData managedObjectContext] objectWithID:deletedAppleObjectID];

moc = [appleClone managedObjectContext];

if (!moc)
    NSLog(@"5 - Deleted.");
else
    NSLog(@"5 - Not deleted.");   // This prints. -managedObjectContext does not return nil!


// 6) Finally, let's use the method I wrote, -hasManagedObjectBeenDeleted:
BOOL deleted = [coreData hasManagedObjectBeenDeleted:appleClone];

if (deleted)
    NSLog(@"6 - Deleted.");       // This prints.
else
    NSLog(@"6 - Not deleted.");

这是打印输出:

2 - Not deleted.
3 - Not deleted.
4 - Deleted.
5 - Not deleted.
6 - Deleted.

如您所见,-managedObjectContext如果从持久性存储中删除了NSManagedObject ,则将不会总是返回nil。


1
有趣的是,尽管看起来这不适用于没有属性的对象。另外,为什么不使用existingObjectWithID:error:代替objectWithID:而仅检查返回值是否相等nil
James Huddleston

嗯,您是对的,-existingObjectWithID:error:是更好的方法!:)我写的答案与Mac OS X 10.5+兼容,所以我忽略了该方法,该方法仅10.6+。是的,我的答案不适用于没有任何属性的对象,尽管您的数据模型中不太可能有空对象。
戴夫

你是对的。对象不可能没有属性,包括关系。由于某种原因,我只考虑属性。嗯...有没有一种方法可以快速评估返回的故障objectWithID:而不检查所有属性?(访问所有属性对于删除的对象而言可能会变得昂贵。)如果存在一个会引发故障的方法,则只需在返回的对象上调用该方法objectWithID:以查看其是否确实存在。我正在寻找这样的方法,但是没有发现任何明显的问题。
James Huddleston

我想一种更好的优化方法是只查询单个属性。代替for循环,只需运行(void)[managedObjectClone valueForKey:[propertyNames objectAtIndex:0]];一次。对于已删除的对象,它将引发错误,尝试从持久性存储中读取并NSObjectInaccessibleException立即引发。如果未提高NSObjectInaccessibleException,则表示它已从持久性存储中成功读取,并且未删除该对象。如果您在索引0处的“随机”属性可能非常庞大,例如100MB二进制NSData,那么对其进行优化将非常棘手……
Dave

2
这将使方法变得更长,但是为什么不还提前给“ isDeleted”一个调用,如果有则立即返回呢?目前它可能会说将要删除的内容将不会是,这可能是坏的……
Kendall Helmstetter Gelner 2012年

28

我担心其他答案中的讨论实际上隐藏了正确答案的简单性。在几乎所有情况下,正确的答案是:

if ([moc existingObjectWithID:object.objectID error:NULL])
{
    // object is valid, go ahead and use it
}

此答案不适用的唯一情况是:

  1. 如果您以Mac OS 10.5或更早版本为目标
  2. 如果您定位的是iOS 2.0或更早版本
  3. 如果对象/上下文尚未保存(在这种情况下,您可以忽略,因为它不会抛出NSObjectInaccessibleException,也可以使用object.isDeleted

2
我担心这个问题的复杂性甚至没有得到充分探讨:假设存在并发环境,其结果将[moc existingObjectWithID:object.objectID error:NULL])]立即失效。因此,即使我们对此进行测试并得到“是”,另一个上下文也可能删除该对象并保存该上下文。随后save发送到前一个上下文的对象现在将引发异常。更糟糕的是,内部Core Data可能会使用Blocks并将它们同步分派到另一个线程,然后再在该线程中发生此异常,这使得在调用站点上尝试捕获catch块变得毫无用处。
CouchDeveloper 2014年

2
我不相信这是真的。受管对象上下文为持久性存储创建快照,并且在合并更改或从存储中获取数据之前,托管对象上下文不会受到其他上下文或存储上操作的影响。只要合并是在与执行代码的相同线程(例如主线程)上执行的,existingObjectWithID:则将依次处理每个线程,并且该对象仅在合并之后才过时。
马特

14

由于我最近在依靠Core Data来实现持久性的iOS应用中实现iCloud的经验,我意识到最好的方法是观察框架的通知。至少比依靠某些晦涩的方法(可能会或可能不会告诉您是否删除了某些托管对象)更好。

对于“纯” Core Data应用程序,应在主线程上观察NSManagedObjectContextObjectsDidChangeNotification。通知的用户信息字典包含具有插入,删除和更新的受管理对象的objectID的集合。

如果您在这些集合之一中找到托管对象的objectID,则可以用某种不错的方式更新应用程序和UI。

就这样...有关更多信息,请访问Apple的《核心数据编程指南》,“与核心数据并发”一章。有一个部分“使用通知跟踪其他线程中的更改”,但是请不要忘记检查前面的“使用线程限制来支持并发性”。


这确实是最好的方法,当然也不难。
Charles A.

0

在Swift 3,Xcode 7.3中验证

您还可以简单地PRINT每个上下文的内存引用并检查

(a) if the context exists,
(b) if the contexts of 2 objects are different

例如:(书和会员是两个不同的对象)

 print(book.managedObjectContext)
 print(member.managedObjectContext)

如果上下文存在但不同,它将打印类似这样的内容

0x7fe758c307d0
0x7fe758c15d70
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.