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

前端 未结 5 1568
[愿得一人]
[愿得一人] 2020-11-27 11:06

I have an NSManagedObject that has been deleted, and the context containing that managed object has been saved. I understand that isDeleted returns

5条回答
  •  慢半拍i
    慢半拍i (楼主)
    2020-11-27 11:34

    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.

提交回复
热议问题