71 votes

Comment puis-je 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é sauvegardé. Je comprends que isDeleted renvoie à YES si Core Data demande au magasin persistant de supprimer l'objet lors de la prochaine opération de sauvegarde. Cependant, puisque la sauvegarde a déjà eu lieu, isDeleted renvoie à NO .

Quel est le bon moyen de savoir si un NSManagedObject a été supprimé après le contexte qui le contient a été sauvegardé ?

(Au cas où vous vous demanderiez pourquoi l'objet se référant à l'objet géré supprimé n'est pas déjà au courant de la suppression, c'est parce que la suppression et la sauvegarde du contexte ont été initiées par un thread d'arrière-plan qui a effectué la suppression et la sauvegarde en utilisant la méthode suivante performSelectorOnMainThread:withObject:waitUntilDone: .)

94voto

James Huddleston Points 5061

La vérification du contexte de l'objet géré semble fonctionner :

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

Extrait de la documentation d'Apple sur managedObjectContext ...

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

Si le récepteur est un défaut, l'appel de cette méthode n'entraîne pas son déclenchement.

Ces deux éléments semblent être de bonnes choses.

UPDATE : Si vous essayez de tester si un objet géré récupéré spécifiquement à l'aide de l'option objectWithID: a été supprimé, consultez Réponse de Dave Gallagher . Il fait remarquer que si vous appelez objectWithID: en utilisant l'ID d'un objet supprimé, l'objet renvoyé sera une faute qui ne pas a son managedObjectContext défini comme nul. Par conséquent, vous ne pouvez pas simplement vérifier son managedObjectContext pour vérifier s'il a été supprimé. Utilisez existingObjectWithID:error: si vous le pouvez. Si ce n'est pas le cas, par exemple si vous visez Mac OS 10.5 ou iOS 2.0, vous devrez faire autre chose pour tester la suppression. Voir sa réponse pour les détails.

0 votes

Il existe également une méthode isInserted renvoyant un BOOL sur NSManagedObject qui, d'après ce que j'ai compris, signifie la même chose. Il est probablement un peu plus propre de l'utiliser dans ce cas.

0 votes

Quoi qu'il en soit, dans la plupart des cas, cette vérification de managedObjectContext est suffisante et rapide !

1 votes

@de, isInserted est seulement OUI jusqu'à ce que l'objet soit sauvegardé, puis il devient NON. La documentation ne le dit pas, mais mes tests le prouvent.

43voto

Dave Gallagher Points 5314

UPDATE : Une réponse améliorée, basée sur 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.");
    }
}

VIEILLE RÉPONSE/RÉPRIMÉE :

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ée dans sa réponse, en vérifiant si l'objet NSManagedObject -managedObjectContext renvoie à nil est un moyen "assez bon" de voir si un NSManagedObject en cache/stale a été supprimé du Persistent Store, mais il n'est pas toujours précis comme Apple l'indique dans sa documentation :

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

Quand ne retournera-t-il pas nil ? Si vous faites l'acquisition d'un autre NSManagedObject en utilisant la fonction -objectID comme ça :

// 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 voir, -managedObjectContext ne retournera pas toujours nil si un NSManagedObject a été supprimé du Persistent Store.

1 votes

Intéressant, mais il semble que cela ne fonctionnera pas pour les objets qui n'ont pas de propriétés. Aussi, pourquoi ne pas utiliser existingObjectWithID:error: au lieu de objectWithID: et vérifie simplement si la valeur de retour est égale à nil ?

0 votes

Ah, vous avez raison, -existingObjectWithID:error: est une meilleure solution ! :) J'ai écrit la réponse pour qu'elle soit compatible avec Mac OS X 10.5+, j'ai donc ignoré cette méthode, qui ne fonctionne qu'avec 10.6+. Et oui, ma réponse ne fonctionnera pas pour un objet sans aucune propriété, bien qu'il soit peu probable d'avoir des objets vides dans votre modèle de données.

0 votes

Vous avez raison. Il est peu probable que les objets n'aient pas de propriétés, ce qui inclut les relations. Pour une raison quelconque, je pensais uniquement aux attributs. Hmm... existe-t-il un moyen d'évaluer rapidement la faute renvoyée par objectWithID: sans vérifier toutes les propriétés ? (L'accès à toutes les propriétés pourrait s'avérer coûteux pour les objets qui n'ont pas ont été supprimées). S'il n'y avait qu'une seule méthode pour déclencher l'erreur, vous pourriez simplement appeler cette méthode sur l'objet retourné par objectWithID: pour savoir s'il existe réellement ou non. J'ai cherché une telle méthode, mais je n'ai rien trouvé d'évident.

29voto

JosephH Points 21074

Je crains que la discussion dans les autres réponses ne cache en fait la simplicité de la réponse correcte. 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 dans lesquels cette réponse ne s'applique pas sont les suivants :

  1. Si vous ciblez Mac OS 10.5 ou antérieur
  2. Si vous ciblez iOS 2.0 ou antérieur
  3. Si l'objet/contexte n'a pas encore été sauvegardé (dans ce cas, soit vous ne vous en préoccupez pas, car il n'y aura pas de message d'erreur de type NSObjectInaccessibleException ou vous pouvez utiliser object.isDeleted )

2 votes

Je ne crois pas que cela soit vrai. Le contexte de l'objet géré prend un instantané du magasin persistant et n'est pas affecté par les opérations sur les autres contextes ou le magasin jusqu'à ce qu'il fusionne les changements ou récupère les données du magasin. Tant que la fusion est effectuée sur le même thread (par exemple, le thread principal) que le code qui exécute le existingObjectWithID: chacun sera traité dans l'ordre, et l'objet ne sera périmé qu'après la fusion.

15voto

rmartinsjr Points 351

Suite à ma récente expérience d'implémentation d'iCloud dans mon application iOS qui s'appuie sur Core Data pour la persistance, j'ai réalisé que le meilleur moyen est d'observer les notifications du framework. Du moins, c'est mieux que de s'appuyer sur des méthodes obscures qui peuvent, ou non, vous dire si un objet géré a été supprimé.

Pour les applications Core Data "pures", vous devez respecter les règles suivantes NSManagedObjectContextObjectsDidChangeNotification sur le fil principal. Le dictionnaire des informations utilisateur de la notification contient des ensembles avec les objectID des objets gérés qui ont été insérés, supprimés et mis à jour.

Si vous trouvez l'objectID de votre objet géré dans l'un de ces ensembles, vous pouvez alors mettre à jour votre application et votre interface utilisateur d'une manière agréable.

C'est tout... pour plus d'informations, donnez une chance au Guide de programmation Core Data d'Apple, chapitre Concurrence avec Core Data. Il y a une section "Track changes in other Threads using Notifications", mais n'oubliez pas de consulter la section précédente "Use Thread Confinement to Support Concurrency".

0 votes

C'est vraiment la meilleure approche, et elle n'est certainement pas difficile.

0voto

Naishta Points 5026

Vérifié en Swift 3, Xcode 7.3

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

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

ex :( Livre et Membre étant 2 objets différents)

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

Si les contextes existent mais sont différents, il s'affichera quelque chose comme ceci

0x7fe758c307d0
0x7fe758c15d70

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X