2010-12-02 20 views
64

Ich habe eine , die gelöscht wurde, und der Kontext, der das verwaltete Objekt enthält, wurde gespeichert. Ich verstehe, dass isDeletedYES zurückgibt, wenn Core Data den persistenten Speicher anfordert, das Objekt während des nächsten Speichervorgangs zu löschen. Da das Speichern jedoch bereits erfolgt ist, gibt isDeletedNO zurück.Wie kann ich feststellen, ob ein `NSManagedObject` gelöscht wurde?

Was ist ein guter Weg zu sagen, ob ein NSManagedObject gelöscht wurde nach sein content contains gespeichert wurde?

(Falls Sie sich wundern, warum das Objekt, das auf das gelöschte verwaltete Objekt verweist, den Löschvorgang noch nicht bemerkt hat, weil der Lösch- und Kontextsicherungsvorgang von einem Hintergrundthread ausgelöst und mit performSelectorOnMainThread:withObject:waitUntilDone: gespeichert wurde scheint zu funktionieren)

Antwort

85

den Kontext des verwalteten Objekts prüfen.

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

von Dokumentation von Apple auf managedObjectContext ...

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

If the receiver is a fault, calling this method does not cause it to fire.

Beide scheinen gute Dinge zu sein.

UPDATE: Wenn Sie, ob ein verwaltetes Objekt abgerufen zu testen, sind versucht, speziell objectWithID: mit gelöscht wurde, überprüfen Dave Gallagher's answer aus. Er weist darauf hin, dass, wenn Sie objectWithID: unter Verwendung der ID eines gelöschten Objekts aufrufen, das zurückgegebene Objekt ein Fehler sein wird, der nichtmanagedObjectContext auf Null gesetzt hat. Folglich können Sie nicht einfach seine managedObjectContext überprüfen, um zu testen, ob es gelöscht wurde. Verwenden Sie existingObjectWithID:error:, wenn Sie können. Wenn Sie nicht beispielsweise auf Mac OS 10.5 oder iOS 2.0 abzielen, müssen Sie etwas anderes tun, um das Löschen zu testen. Einzelheiten finden Sie unter his answer.

+0

Es gibt auch eine Methode 'isInserted' ein BOOL auf NSManagedObject Rückkehr, die mein Verständnis, das gleiche bedeutet. Es ist wahrscheinlich ein bisschen sauberer, es für diesen Fall zu verwenden. –

+0

Wie auch immer, in den meisten Fällen ist diese managedObjectContext-Prüfung ausreichend und schnell! – flypig

+0

@de, 'isInserted' ist nur JA bis das Objekt gespeichert wird, und dann wird es NEIN. Die Dokumentation sagt das nicht, aber meine Tests beweisen es. – phatmann

38

UPDATE: Eine verbesserte Antwort, basierend auf James Huddleston ‚s Ideen in der Diskussion unten.

- (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/abgewertet ANTWORT:

ich eine etwas bessere Methode geschrieben haben. self ist Ihr 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. 
    } 
} 

Wie James Huddleston in seiner Antwort erwähnt, zu überprüfen, ob NSManagedObject des -managedObjectContext kehrt nil eine „ziemlich gut“ Art und Weise ist zu sehen, wenn ein im Cache gespeicherte/abgestanden NSManagedObject wird aus dem persistenten Speicher gelöscht , aber es ist nicht immer genau, wie Apple-Staaten in ihren docs:

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

Wann wird es nicht gleich Null zurückgeben?Wenn Sie einen anderen NSManagedObject die gelöschte NSManagedObject der Verwendung erwerben -objectID wie 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."); 

Hier ist der Ausdruck:

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

Wie Sie sehen können, wird -managedObjectContext nicht immer zurückkehren Null, wenn ein NSManagedObject wurde gelöscht aus dem dauerhaften Speicher.

+1

Interessant, obwohl es so aussieht, dass dies nicht für Objekte funktioniert, die keine Eigenschaften haben. Warum nicht 'existingObjectWithID: error:' anstelle von 'objectWithID:' verwenden und einfach prüfen, ob der Rückgabewert gleich 'nil' ist? –

+0

Ah, du bist richtig, '-existingObjectWithID: error:' ist ein besserer Weg! :) Ich schrieb die Antwort, um mit Mac OS X 10.5+ kompatibel zu sein, also ignorierte ich diese Methode, die nur 10.6+ ist. Und ja, meine Antwort wird nicht für ein Objekt ohne Eigenschaften funktionieren, obwohl es unwahrscheinlich ist, dass leere Objekte in Ihrem Datenmodell vorhanden sind. –

+0

Sie haben Recht. Es ist unwahrscheinlich, dass Objekte keine Eigenschaften haben, einschließlich Beziehungen. Aus irgendeinem Grund habe ich nur an Attribute gedacht. Hmm ... gibt es eine Möglichkeit, den von 'objectWithID:' zurückgegebenen Fehler schnell auszuwerten, ohne alle Eigenschaften zu überprüfen? (Der Zugriff auf jede Eigenschaft könnte für Objekte, die * nicht * gelöscht wurden, teuer werden.) Wenn es eine einzige Methode gäbe, die den Fehler auslöst, könnten Sie diese Methode für das Objekt 'objectWithID:' aufrufen, um zu sehen, ob dies der Fall ist existiert wirklich oder nicht. Ich suchte nach einer solchen Methode, fand aber nichts Offensichtliches. –

11

Aufgrund meiner jüngsten Erfahrung bei der Implementierung von iCloud in meiner iOS-App, die auf Core-Daten für Persistenz beruht, habe ich festgestellt, dass die Überwachung der Framework-Benachrichtigungen der beste Weg ist. Zumindest besser, als sich auf einige obskure Methoden zu verlassen, die Ihnen sagen können, ob ein verwaltetes Objekt gelöscht wurde oder nicht.

Für 'pure' Core Data-Anwendungen sollten Sie beobachten NSManagedObjectContextObjectsDidChangeNotification auf dem Hauptthread. Das Benutzerinformationswörterverzeichnis der Benachrichtigung enthält Gruppen mit den Objekt-IDs der verwalteten Objekte, die eingefügt, gelöscht und aktualisiert wurden.

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

Das ist es ... für weitere Informationen, geben Sie eine Chance auf Apples Core Data Programming Guide, Concurrency mit Core Data Kapitel. Es gibt einen Abschnitt "Verfolgen Sie Änderungen in anderen Threads mit Benachrichtigungen", aber vergessen Sie nicht, das vorherige "Thread Confinement zur Unterstützung von Concurrency" zu überprüfen.

25

Ich fürchte, die Diskussion in den anderen Antworten ist eigentlich die Einfachheit der richtigen Antwort versteckt. In so ziemlich allen Fällen die richtige Antwort ist:

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

Die einzigen Fälle, diese Antwort gilt nicht ist:

  1. Wenn Sie Mac OS 10.5 oder früher
  2. Wenn Sie Targeting sind Targeting iOS 2.0 oder früher
  3. Wenn das Objekt/Kontext noch nicht gespeichert wurde (in diesem Fall, dass Sie entweder nicht kümmern, da es keine NSObjectInaccessibleException werfen, oder können Sie object.isDeleted verwenden)
+2

Ich fürchte, die Komplexität dieses Problems ist noch nicht vollständig erforscht: Unter der Annahme einer _concurrent_ Umgebung ist das Ergebnis von [[moc existingObjectWithID: object.objectID error: NULL])] sofort veraltet. Selbst wenn wir dies testen und ein "JA" erhalten, kann ein anderer Kontext das Objekt löschen und den Kontext speichern. Der nachfolgende 'save', der an den vorherigen Kontext gesendet wird, löst jetzt eine Ausnahme aus. Schlimmer noch, interne Core-Daten können Blöcke verwenden und diese synchron an einen anderen Thread senden, an dem diese Ausnahme auftritt, wodurch die try- und catch-Blöcke auf der Call-Site unbrauchbar werden. – CouchDeveloper

+1

Ich glaube nicht, dass das wahr ist. Der Kontext des verwalteten Objekts erstellt einen Snapshot des persistenten Speichers und wird nicht von Operationen in anderen Kontexten oder im Speicher beeinflusst, bis er Änderungen zusammenführt oder Daten aus dem Speicher abruft. Solange die Zusammenführung für denselben Thread (z. B. den Hauptthread) ausgeführt wird wie der Code, der 'existingObjectWithID:' ausführt, wird jeder in der Folge verarbeitet, und das Objekt wird erst nach der Zusammenführung veraltet sein. – Matt

1

versuchen diese Methode:

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

Bestätigte in Swift 3, Xcode 7.3

Sie können auch einfach PRINT die Speicherreferenzen von jedem Kontext 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äre so etwas wie dies druckt, wenn die Kontexte existieren, sind aber unterschiedliche

0x7fe758c307d0 
0x7fe758c15d70 
Verwandte Themen