web-dev-qa-db-fra.com

Comment savoir si un `NSManagedObject` a été supprimé?

J'ai un NSManagedObject qui a été supprimé et le contexte contenant cet objet géré a été enregistré. Je comprends que isDeleted retourne YES si Core Data demandera au magasin persistant de supprimer l’objet lors de la prochaine opération de sauvegarde. Cependant, étant donné que la sauvegarde a déjà eu lieu, isDeleted renvoie NO.

Quel est un bon moyen de savoir si une NSManagedObject a été supprimée après son contexte contenant a été enregistré?

(Si vous vous demandez pourquoi l'objet faisant référence à l'objet géré supprimé n'est pas déjà informé de la suppression, c'est parce que la suppression et la sauvegarde du contexte ont été lancées par un thread d'arrière-plan qui a effectué la suppression et la sauvegarde à l'aide de performSelectorOnMainThread:withObject:waitUntilDone:.)

66
James Huddleston

Vérifier le contexte de l'objet géré semble fonctionner:

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

D'après la documentation d'Apple sur managedObjectContext ...

Cette méthode peut renvoyer nil si le récepteur a été supprimé de son le contexte.

Si le destinataire est en faute, appelez cette méthode ne provoque pas de feu.

Les deux semblent être de bonnes choses.

UPDATE: Si vous essayez de vérifier si un objet géré récupéré à l'aide de objectWithID: a été supprimé, consultez la réponse de Dave Gallagher . Il souligne que si vous appelez objectWithID: à l'aide de l'ID d'un objet supprimé, l'objet renvoyé sera une erreur qui fait que non a sa valeur managedObjectContext définie sur nil. Par conséquent, vous ne pouvez pas simplement vérifier sa managedObjectContext pour vérifier si elle a été supprimée. Utilisez existingObjectWithID:error: si vous le pouvez. Si ce n'est pas le cas, par exemple, si vous ciblez Mac OS 10.5 ou iOS 2.0, vous devrez faire autre chose pour tester la suppression. Voir sa réponse pour plus de détails.

90
James Huddleston

MISE À JOUR: Une réponse améliorée, basée sur les idées de James Huddleston dans la discussion ci-dessous.

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

RÉPONSE ANCIENNE/DÉPRECIEE:

J'ai écrit une méthode légèrement meilleure. self est votre classe/contrôleur Core Data.

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

Comme James Huddleston mentionné dans sa réponse, vérifier si le -managedObjectContext de __ManagedObject de NSManagedObject est nil est un "très bon" moyen de voir si un NSManagedObject mis en cache/obsolète a été supprimé du magasin persistant, mais il n'est pas toujours précis états dans leurs docs:

Cette méthode peut renvoie la valeur nil si le récepteur a été supprimé de son fichier le contexte.

Quand ne reviendra-t-il pas à zéro? Si vous acquérez un objet NSManagedObject différent à l'aide du -objectID de NSManagedObject supprimé, procédez comme suit:

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

Voici l'impression:

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

Comme vous pouvez le constater, -managedObjectContext ne renverra pas toujours nil si un objet NSManagedObject a été supprimé du magasin persistant.

39
Dave Gallagher

Je crains que la discussion dans les autres réponses ne cache en réalité la simplicité de la bonne réponse. Dans presque tous les cas, la réponse correcte est:

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

Les seuls cas où cette réponse ne s'applique pas sont:

  1. Si vous ciblez Mac OS 10.5 ou une version antérieure
  2. Si vous ciblez iOS 2.0 ou une version antérieure
  3. Si l'objet/le contexte n'a pas encore été enregistré (dans ce cas, vous ne vous en souciez pas car il ne lancera pas une variable NSObjectInaccessibleException ou vous pouvez utiliser object.isDeleted)
27
JosephH

En raison de ma récente expérience de la mise en œuvre d'iCloud dans mon application iOS reposant sur la persistance de Core Data, j'ai compris que l'observation des notifications du cadre était la meilleure solution. Au moins, mieux que de compter sur des méthodes obscures qui peuvent ou ne peuvent pas vous dire si un objet géré a été supprimé.

Pour les applications Core Data «pures», vous devez observer NSManagedObjectContextObjectsDidChangeNotification sur le thread principal. Le dictionnaire d'informations utilisateur de la notification contient des ensembles avec les ID d'objet des objets gérés insérés, supprimés et mis à jour.

Si vous trouvez l'ID d'objet de votre objet géré dans l'un de ces ensembles, vous pouvez mettre à jour votre application et votre interface utilisateur de façon conviviale.

C'est tout ... pour plus d'informations, laissez-vous tenter par le chapitre Guide de programmation Core Data d'Apple, Concurrence avec données de base. Il existe une section "Suivre les modifications dans d'autres threads à l'aide de notifications", mais n'oubliez pas de cocher la précédente "Utiliser le confinement de threads pour prendre en charge la concurrence".

11
rmartinsjr

essayez cette méthode:

if (manageObject.deleted) {
    // assume that the managed object has been deleted.
}
1
dusty

Vérifié dans Swift 3, Xcode 7.3

Vous pouvez aussi simplement PRINT les références de mémoire de chaque contexte et vérifier

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

ex :( Book et Member étant 2 objets différents)

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

Cela imprimerait quelque chose comme ceci si les contextes existent mais sont différents

0x7fe758c307d0
0x7fe758c15d70
0
Naishta