Wie kann ich feststellen, ob ein "NSManagedObject" gelöscht wurde?

70

Ich habe eine NSManagedObject, die gelöscht wurde, und der Kontext, der dieses verwaltete Objekt enthält, wurde gespeichert. Ich verstehe, dass dies isDeletedzurückgegeben wird, YESwenn Core Data den persistenten Speicher auffordert, das Objekt beim nächsten Speichervorgang zu löschen. Da das Speichern jedoch bereits erfolgt ist, wird isDeletedzurückgegeben NO.

Was ist ein guter Weg, um festzustellen, ob ein NSManagedObjectgelöscht wurde, nachdem sein enthaltener Kontext gespeichert wurde?

(Falls Sie sich fragen, warum das Objekt, das auf das gelöschte verwaltete Objekt verweist, die Löschung noch nicht kennt, liegt dies daran, dass das Löschen und Speichern des Kontexts von einem Hintergrundthread initiiert wurde, der das Löschen und Speichern mit durchgeführt hat performSelectorOnMainThread:withObject:waitUntilDone: .)

James Huddleston
quelle

Antworten:

93

Das Überprüfen des Kontexts des verwalteten Objekts scheint zu funktionieren:

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

Aus Apples Dokumentation zu managedObjectContext...

Diese Methode kann null zurückgeben, wenn der Empfänger aus seinem Kontext gelöscht wurde.

Wenn der Empfänger ein Fehler ist, wird er durch Aufrufen dieser Methode nicht ausgelöst.

Beide scheinen gute Dinge zu sein.

UPDATE: Wenn Sie testen möchten, ob ein speziell mit abgerufenes verwaltetes Objekt objectWithID:gelöscht wurde, lesen Sie die Antwort von Dave Gallagher . Er weist darauf hin , dass , wenn Sie rufen objectWithID:die ID eines gelöschten Objekts verwenden, zurückgegeben das Objekt wird ein Fehler sein, der sich nicht seine haben managedObjectContextSatz zu null. Folglich können Sie nicht einfach überprüfen managedObjectContext, ob es gelöscht wurde. Verwenden existingObjectWithID:error:Sie, wenn Sie können. Wenn Sie nicht auf Mac OS 10.5 oder iOS 2.0 abzielen, müssen Sie etwas anderes tun, um das Löschen zu testen. Siehe seine Antwort für Details.

James Huddleston
quelle
Es gibt auch eine Methode, isInserteddie ein BOOL auf NSManagedObject zurückgibt, was meines Wissens dasselbe bedeutet. Es ist wahrscheinlich etwas sauberer, es für diesen Fall zu verwenden.
de.
In den meisten Fällen ist diese verwaltete Objektkontextprüfung jedoch ausreichend und schnell!
Flypig
1
@de isInsertedist nur JA, bis das Objekt gespeichert ist, und dann wird es NEIN. Die Dokumentation sagt dies nicht, aber meine Tests beweisen es.
Phatmann
1
Das Testen unter iOS 7 und das Löschen eines Objekts, bei dem das Löschen dann in einen Hauptthreadkontext und den verwalteten Objektkontext eingefügt wurde, ist für Referenzen, die für dieses Objekt aus dem Hauptthreadkontext gespeichert wurden, gleich Null. Der Versuch, das Objekt anhand der ID oder anderer Abrufeigenschaften abzurufen, gibt null zurück.
jjxtra
43

UPDATE: Eine verbesserte Antwort, basierend auf James Huddlestons Ideen in der folgenden Diskussion.

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

ALTE / VERRÜCKTE ANTWORT:

Ich habe eine etwas bessere Methode geschrieben. selfist Ihre Core Data-Klasse / 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.
    }
}

Wie James Huddleston in seiner Antwort erwähnte, ist die Überprüfung, ob die -managedObjectContextRückgabe von NSManagedObject nileine "ziemlich gute" Methode ist, um festzustellen, ob ein zwischengespeichertes / veraltetes NSManagedObject aus dem persistenten Speicher gelöscht wurde, aber nicht immer korrekt, wie Apple in seinen Dokumenten feststellt:

Diese Methode kann null zurückgeben, wenn der Empfänger aus seinem Kontext gelöscht wurde.

Wann wird es nicht null zurückgeben? Wenn Sie ein anderes NSManagedObject mit den gelöschten NSManagedObjects erwerben, gehen Sie -objectIDwie folgt vor :

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

Hier ist der Ausdruck:

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

Wie Sie sehen, -managedObjectContextwird nicht immer nil zurückgegeben, wenn ein NSManagedObject aus dem Persistent Store gelöscht wurde.

Dave
quelle
1
Interessant, obwohl es so aussieht, als würde dies bei Objekten ohne Eigenschaften nicht funktionieren. Verwenden Sie existingObjectWithID:error:stattdessen anstelle von objectWithID:und prüfen Sie einfach, ob der Rückgabewert gleich ist nil.
James Huddleston
Ah, du hast recht, -existingObjectWithID:error:ist ein besserer Weg! :) Ich habe die Antwort geschrieben, um mit Mac OS X 10.5+ kompatibel zu sein, also habe ich diese Methode ignoriert, die nur 10.6+ ist. Und ja, meine Antwort funktioniert nicht für ein Objekt ohne Eigenschaften, obwohl es unwahrscheinlich ist, dass Ihr Datenmodell leere Objekte enthält.
Dave
Du hast recht. Es ist unwahrscheinlich, dass Objekte keine Eigenschaften haben, einschließlich Beziehungen. Aus irgendeinem Grund dachte ich nur an Attribute. Hmm ... gibt es eine Möglichkeit, den zurückgegebenen Fehler schnell zu bewerten, objectWithID:ohne alle Eigenschaften zu überprüfen? (Der Zugriff auf jede Eigenschaft kann für Objekte, die nicht gelöscht wurden , teuer werden .) Wenn es eine einzige Methode gibt, die den Fehler auslöst, können Sie diese Methode einfach für das zurückgegebene Objekt aufrufen, objectWithID:um festzustellen, ob sie tatsächlich vorhanden ist oder nicht. Ich suchte nach einer solchen Methode, fand aber nichts Offensichtliches.
James Huddleston
Ich denke, ein besserer Weg zur Optimierung wäre, nur eine einzelne Eigenschaft abzufragen. Führen Sie anstelle der for-Schleife nur (void)[managedObjectClone valueForKey:[propertyNames objectAtIndex:0]];einmal aus. Bei einem gelöschten Objekt wird ein Fehler ausgelöst, versucht, aus dem persistenten Speicher zu lesen, und NSObjectInaccessibleExceptionsofort ausgelöst . Wenn es nicht ausgelöst NSObjectInaccessibleExceptionwird, bedeutet dies, dass es erfolgreich aus dem persistenten Speicher gelesen wurde und das Objekt nicht gelöscht wurde. Wenn Ihre "zufällige" Eigenschaft bei Index 0 sehr groß sein könnte, wie z. B. 100 MB binäre NSData, wäre eine Optimierung schwierig ...
Dave
2
Dies wird die Methode noch länger machen, aber warum nicht auch "isDeleted" im Voraus aufrufen und diese sofort zurückgeben, wenn dies der Fall ist? Derzeit könnte man sagen, dass etwas, das gelöscht werden soll, nicht sein wird, was schlecht sein könnte ...
Kendall Helmstetter Gelner
28

Ich befürchte, dass die Diskussion in den anderen Antworten tatsächlich die Einfachheit der richtigen Antwort verbirgt. In so ziemlich allen Fällen lautet die richtige Antwort:

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

Die einzigen Fälle, in denen diese Antwort nicht zutrifft, sind:

  1. Wenn Sie auf Mac OS 10.5 oder früher abzielen
  2. Wenn Sie auf iOS 2.0 oder früher abzielen
  3. Wenn das Objekt / der Kontext noch nicht gespeichert wurde (in diesem Fall ist es Ihnen entweder egal, weil es kein a auslöst NSObjectInaccessibleException, oder Sie können es verwenden object.isDeleted)
JosephH
quelle
2
Ich befürchte, dass die Komplexität dieses Problems noch nicht vollständig erforscht ist: Unter der Annahme einer gleichzeitigen Umgebung ist das Ergebnis [moc existingObjectWithID:object.objectID error:NULL])]sofort veraltet. Selbst wenn wir dies testen und ein "JA" erhalten würden, könnte ein anderer Kontext das Objekt löschen und den Kontext speichern. Das nachfolgende saveSenden an den vorherigen Kontext löst jetzt eine Ausnahme aus. Schlimmer noch, intern können Core Data Blöcke verwenden und diese synchron an einen anderen Thread senden, wo diese Ausnahme dann auftritt, wodurch das Versuch, Blöcke auf der Aufrufstelle abzufangen, unbrauchbar wird.
CouchDeveloper
2
Ich glaube nicht, dass das wahr ist. Der verwaltete Objektkontext erstellt eine Momentaufnahme des persistenten Speichers und wird erst dann von Vorgängen in anderen Kontexten oder im Speicher beeinflusst, wenn Änderungen zusammengeführt oder Daten aus dem Speicher abgerufen werden. Solange die Zusammenführung für denselben Thread (z. B. den Hauptthread) wie der Code ausgeführt wird, existingObjectWithID:wird jeder nacheinander verarbeitet, und das Objekt ist erst nach der Zusammenführung veraltet.
Matt
14

Aufgrund meiner jüngsten Erfahrung mit der Implementierung von iCloud in meiner iOS-App, die für die Persistenz auf Core Data basiert, wurde mir klar, dass der beste Weg darin besteht, die Benachrichtigungen des Frameworks zu beobachten. Zumindest besser, als sich auf einige obskure Methoden zu verlassen, die Ihnen möglicherweise mitteilen oder nicht, ob ein verwaltetes Objekt gelöscht wurde.

Für 'reine' Core Data-Apps sollten Sie NSManagedObjectContextObjectsDidChangeNotification beachten im Hauptthread . Das Benutzerinformationswörterbuch der Benachrichtigung enthält Sätze mit den Objekt-IDs der verwalteten Objekte, die eingefügt, gelöscht und aktualisiert wurden.

Wenn Sie die Objekt-ID Ihres verwalteten Objekts in einem dieser Sätze finden, können Sie Ihre Anwendung und Benutzeroberfläche auf eine nette Weise aktualisieren.

Das war's ... Weitere Informationen finden Sie im Apple Core Data Programming Guide, Kapitel Parallelität mit Core Data. Es gibt einen Abschnitt "Verfolgen von Änderungen in anderen Threads mithilfe von Benachrichtigungen". Vergessen Sie jedoch nicht, den vorherigen Abschnitt "Verwenden der Thread-Beschränkung zur Unterstützung der Parallelität" zu überprüfen.

rmartinsjr
quelle
Dies ist wirklich der beste Ansatz, und es ist sicherlich nicht schwierig.
Charles A.
0

Verifiziert in Swift 3, Xcode 7.3

Sie können auch einfach PRINTdie Speicherreferenzen jedes Kontexts anzeigen und überprüfen

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

zB :( Buch und Mitglied sind 2 verschiedene Objekte)

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

Es würde so etwas drucken, wenn die Kontexte existieren, aber unterschiedlich sind

0x7fe758c307d0
0x7fe758c15d70
Naishta
quelle