[Core-data] How can I tell whether an `NSManagedObject` has been deleted?


Answers

UPDATE: An improved answer, based on James Huddleston's ideas in the discussion below.

- (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.");
    }
}

OLD/DEPRECIATED ANSWER:

I wrote a slightly better method. self is your Core Data class/controller.

- (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.
    }
}

As James Huddleston mentioned in his answer, checking to see if NSManagedObject's -managedObjectContext returns nil is a "pretty good" way of seeing if a cached/stale NSManagedObject has been deleted from the Persistent Store, but it's not always accurate as Apple states in their docs:

This method may return nil if the receiver has been deleted from its context.

When won't it return nil? If you acquire a different NSManagedObject using the deleted NSManagedObject's -objectID like so:

// 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.");

Here's the printout:

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

As you can see, -managedObjectContext won't always return nil if an NSManagedObject has been deleted from the Persistent Store.

Question

I have an NSManagedObject that has been deleted, and the context containing that managed object has been saved. I understand that isDeleted returns YES if Core Data will ask the persistent store to delete the object during the next save operation. However, since the save has already happened, isDeleted returns NO.

What is a good way to tell whether an NSManagedObject has been deleted after its containing context has been saved?

(In case you're wondering why the object referring to the deleted managed object isn't already aware of the deletion, it's because the deletion and context save was initiated by a background thread which performed the deletion and save using performSelectorOnMainThread:withObject:waitUntilDone:.)




After you delete an object, the isDeleted property will be true. After saving the context, the isDeleted will be false if you still have a reference to the managed object.

You can safely make weak references to managed objects. The weak reference will nil out automatically for you under ARC when Core Data is done with them.

Here are the three relevant paragraphs from the Core Data Programming Guide:

Core Data “owns” the life-cycle of managed objects. With faulting and undo, you cannot make the same assumptions about the life-cycle of a managed object as you would of a standard Cocoa object—managed objects can be instantiated, destroyed, and resurrected by the framework as it requires.

When a managed object is created, it is initialized with the default values given for its entity in the managed object model. In many cases the default values set in the model may be sufficient. Sometimes, however, you may wish to perform additional initialization—perhaps using dynamic values (such as the current date and time) that cannot be represented in the model.

You should typically not override dealloc to clear transient properties and other variables. Instead, you should override didTurnIntoFault. didTurnIntoFault is invoked automatically by Core Data when an object is turned into a fault and immediately prior to actual deallocation. You might turn a managed object into a fault specifically to reduce memory overhead (see “Reducing Memory Overhead”), so it is important to ensure that you properly perform clean-up operations in didTurnIntoFault.




Core Data: executeFetchRequest:error: returns objects with no managedObjectContext

This is a symptom of updating the managed object after it has been deleted and its context has been saved.

I was keeping a reference to the Foo instance in a view controller. I deleted all Foo objects from the managed object context, then updated the Foo instance, then tried to delete all Foo objects again.

This question helped me track down the cause: How can I tell whether an `NSManagedObject` has been deleted?.




CoreData detecting deleted entity after save

i found help in this OSX oriented thread

How can I tell whether an `NSManagedObject` has been deleted?

- (BOOL) entityWasDeleted:(SomeEntity *)someEntity {

    return ((someEntity == nil) || ([self.moc existingObjectWithID:someEntity.objectID error:NULL] == nil));
}

Beware : don't use the code bellow, as it might not always work

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