如何判断是否已删除"NSManagedObject"?

Jam*_*ton 68 core-data nsmanagedobject ios

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

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

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

Jam*_*ton 91

检查托管对象的上下文似乎有效:

if (managedObject.managedObjectContext == nil) {
    // Assume that the managed object has been deleted.
}
Run Code Online (Sandbox Code Playgroud)

来自Apple的文档managedObjectContext......

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

如果接收器是故障,调用此方法不会导致它触发.

这些似乎都是好事.

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


Dav*_*her 39

更新:基于James Huddleston在下面的讨论中提出的想法,改进了答案.

- (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.");
    }
}
Run Code Online (Sandbox Code Playgroud)

陈旧/豁免的答案:

我写了一个稍好的方法.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.
    }
}
Run Code Online (Sandbox Code Playgroud)

正如James Huddleston在他的回答中所提到的那样,检查NSManagedObject的-managedObjectContext返回nil是否是一种"相当好"的方式来查看缓存/陈旧的NSManagedObject是否已从持久存储中删除,但它并不总是准确,因为Apple在其文档中说明:

如果接收器已从其上下文中删除,则此方法可能返回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.");
Run Code Online (Sandbox Code Playgroud)

这是打印输出:

2 - Not deleted.
3 - Not deleted.
4 - Deleted.
5 - Not deleted.
6 - Deleted.
Run Code Online (Sandbox Code Playgroud)

如您所见,-managedObjectContext如果已从持久存储中删除NSManagedObject ,则不会始终返回nil.

  • 这将使该方法更长,但为什么不先给"isDeleted"打电话,如果是的话立即返回?目前它可能会说要删除的东西不会是,这可能是坏事...... (2认同)

Jos*_*phH 27

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

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

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

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

  • 我担心这个问题的复杂性甚至没有得到充分的探索:假设_concurrent_环境,`[moc existingObjectWithID:object.objectID错误:NULL])]`的结果立即失效.因此,即使我们测试这个并获得"是",另一个上下文可能会删除该对象并保存上下文.发送到前一个上下文的后续"save"现在将抛出异常.更糟糕的是,内部核心数据可能会使用块并同步将它们分派到另一个线程,然后发生此异常,这使得调用站点上的try和catch块无用. (2认同)
  • 我不相信这是真的。托管对象上下文获取持久存储的快照,并且在合并更改或从存储中获取数据之前,它不受其他上下文或存储上的操作的影响。只要合并是在与执行`existingObjectWithID:` 的代码相同的线程(例如主线程)上执行的,那么每个将按顺序处理,并且对象只会在合并后过时。 (2认同)

rma*_*sjr 13

由于我最近在我的iOS应用程序中实现依赖Core Data持久化的iCloud的经验,我意识到最好的方法是观察框架的通知.至少,比依赖一些模糊的方法更好,这些方法可能会或可能不会告诉您是否删除了某些托管对象.

对于"纯"核心数据应用程序,您应该在主线程上观察NSManagedObjectContextObjectsDidChangeNotification.通知的用户信息字典包含具有插入,删除和更新的托管对象的objectID的集合.

如果您在其中一个集合中找到了托管对象的objectID,那么您可以以一种很好的方式更新您的应用程序和UI.

就是这样......有关更多信息,请给出Apple的核心数据编程指南,核心数据并发章节.有一节"使用通知跟踪其他线程中的更改",但不要忘记检查前一个"使用线程限制来支持并发".