Problem mit Core Data

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Problem mit Core Data

    Ich versuch jetzt schon ein paar Tage, einen funktionierenden Mechanismus zu implementieren, der mir meine komplette Core Data Datenkbank leert (quasi ein Reset-Button in den Einstellungen meiner App). Leider bekomm ichs nicht hin, und die App fliegt mir um die Ohren wenn ich nach dem Löschen versuche weiterzuarbeiten. Über google bin ich auf verschiedene Lösunsansätze gestoßen, keiner davon ging wirklich.

    Fakten:

    In meiner AppDelegate-Klasse habe ich:

    - NSManagedObjectModel *managedObjectModel;
    - NSManagedObjectContext *managedObjectContext;
    - NSPersistentStoreCoordinator *persistentStoreCoordinator;

    Den managedObjectContext gebe ich an meinen RootViewController weiter; von dort aus wird er an 3 Sub-Views weitergegeben (alle per Property & retain flag).
    Diese Methoden habe ich aus den Beispielen (errorhandling weggelassen):

    Quellcode

    1. - (NSManagedObjectContext *) managedObjectContext {
    2. if (managedObjectContext)
    3. return managedObjectContext;
    4. NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    5. if (coordinator) {
    6. managedObjectContext = [[NSManagedObjectContext alloc] init];
    7. [managedObjectContext setPersistentStoreCoordinator: coordinator];
    8. }
    9. return managedObjectContext;
    10. }
    11. - (NSManagedObjectModel *)managedObjectModel {
    12. if (managedObjectModel)
    13. return managedObjectModel;
    14. managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
    15. return managedObjectModel;
    16. }
    17. - (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    18. if (persistentStoreCoordinator)
    19. return persistentStoreCoordinator;
    20. NSURL* storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"CoreData.sqlite"]];
    21. persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    22. [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error];
    23. return persistentStoreCoordinator;
    24. }
    Alles anzeigen



    So, nun mein letzter Versuch, die komplette Datenbank zu löschen:

    Quellcode

    1. - (void) wipeData {
    2. NSArray *stores = [persistentStoreCoordinator persistentStores];
    3. for(NSPersistentStore *store in stores) {
    4. [persistentStoreCoordinator removePersistentStore:store error:nil];
    5. [[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:nil];
    6. }
    7. [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:store.URL options:nil error:&error];
    8. return;
    Alles anzeigen



    Ergebnis:
    Daten werden komplett gelöscht, ich kann wieder zu meinem RootViewController wechseln, eine leere Tabelle wird mir angezeigt. Ich kann auch ein paar mal neue Daten Eintragen, und die Datenbank erneut löschen. Wenn ich dann wieder was eintragen will, crasht es (vermutlich ist es Zufall, wanns crasht):


    Quellcode

    1. 2011-02-24 20:07:03.912 MyApp[1657:207] Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. The NSManagedObject with ID:0x886d000 has been invalidated. with userInfo (null)
    2. Detected an attempt to call a symbol in system libraries that is not present on the iPhone:
    3. _Unwind_Resume called from function -[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] in image CoreData.
    4. 2011-02-24 20:07:03.914 Annual leave[1657:207] *** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'The NSManagedObject with ID:0x886d000 has been invalidated.'
    5. *** Call stack at first throw:
    6. (
    7. (gdb) bt
    8. #0 0x93a74176 in __kill ()
    9. #1 0x93a74168 in kill$UNIX2003 ()
    10. #2 0x93b0689d in raise ()
    11. #3 0x93b1c9bc in abort ()
    12. #4 0x93f2afda in __gnu_cxx::__verbose_terminate_handler ()
    13. #5 0x02954333 in _objc_terminate ()
    14. #6 0x93f2917a in __cxxabiv1::__terminate ()
    15. #7 0x93f291ba in std::terminate ()
    16. #8 0x93f292b8 in __cxa_throw ()
    17. #9 0x02954481 in objc_exception_throw ()
    18. #10 0x02415277 in -[NSManagedObjectContext save:] ()
    19. #11 0x000132d5 in -[MySubViewController saveRecord] (self=0x8830320, _cmd=0x2e780) at /Volumes/Source/App/.../MySubViewController.m:663
    20. #12 0x003107f8 in -[UIApplication sendAction:to:from:forEvent:] ()
    21. #13 0x0051c68b in -[UIBarButtonItem(UIInternal) _sendAction:withEvent:] ()
    22. #14 0x003107f8 in -[UIApplication sendAction:to:from:forEvent:] ()
    23. #15 0x0039bde0 in -[UIControl sendAction:to:forEvent:] ()
    24. #16 0x0039e262 in -[UIControl(Internal) _sendActionsForEvents:withEvent:] ()
    25. #17 0x0039ce0f in -[UIControl touchesEnded:withEvent:] ()
    26. #18 0x003343d0 in -[UIWindow _sendTouchesForEvent:] ()
    27. #19 0x00315cb4 in -[UIApplication sendEvent:] ()
    28. #20 0x0031a9bf in _UIApplicationHandleEvent ()
    Alles anzeigen


    Ich habe auch schon versucht, sowohl den PersistenStoreCoordinator als auch den ManagedObjectContext zu löschen, neu zu erzeugen und an alle SubViewController durchzureichen. Das Ergebnis ist das gleiche. Erst läufts, dann nach ein paar Clicks fliegts mir um die Ohren.

    Was mach ich falsch, und wie mach ichs richtig? :(

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von s710 ()

  • In o.g. Beispiel? Nein. Das hab ich mir von irgendwo her kopiert. Ich habe auch nur ein einziges NSManagedObjectContext Objekt.

    Was ist denn der richtige Weg, die Datenbank zu löschen?

    1.) NSManagedObjectContext löschen
    2.) [persistentStoreCoordinator removePersistentStore:store error:nil];
    3.) NSPersistenStoreCoordinator löschen
    4.) sqlite Datei von Dateisystem löschen
    5.) NSPersistenStoreCoordinator erstellen
    6.) Store hinzufügen
    7.) NSManagedObjectContext erstellen
    8.) NSManagedObjectContext an die Sub-Views weitergeben

    ?
  • Hi,
    eine spontane Idee. Eine leere Datenbank mit dem CoreData Schema im gleichen Verzeichnis ablegen.
    Dort wo du die komplette Datenbank löschst:

    1.Context beenden.
    2.CoreData.sqlite mit nem FileHandle von der Platte fegen.
    3.Die Reservedatenbank duplizieren und als CoreData.sqlite umbenennen.
    4.Context inializeren in gewohnter Weise.

    fertig

    Gruß
    DOCTRINE
  • Wie gesagt war ne spontane Idee. Der Idee dahinter ist, dass Core Data überhaupt nicht mitbekommt, dass sich etwas geändert hat, weil die sqlite im Endeffekt nur ausgetauscht wird.
    Mit nem FileManager sind weniger als 5 Zeilen. Aber es kann sein, dass das ganze an der Migration scheitert, weil Core Data auch cached.
    Ich würde mir mal den Core Data Migration-Guide anschauen, da sind Beispiele wie man den PersistenceStore zur Laufzeit ändert.
  • Ich hatte das, wie von mir oben beschrieben, auch schon probiert, und nicht hinbekommen. Daher die Frage nach dem korrekten Weg. Wenn ichs so versuche:

    Quellcode

    1. - (void) wipeData {
    2. NSURL* storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"CoreData.sqlite"]];
    3. // destroy context
    4. [self.rootViewController resetManagedObjectContext:nil];
    5. [managedObjectContext release];
    6. // remove store
    7. [persistentStoreCoordinator removePersistentStore:[self.persistentStoreCoordinator persistentStoreForURL:storeUrl] error:nil];
    8. [persistentStoreCoordinator release];
    9. // remove sqlite file on filesystem
    10. [[NSFileManager defaultManager] removeItemAtPath:storeUrl.path error:nil];
    11. // create new store
    12. persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    13. [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:nil];
    14. [self.rootViewController resetManagedObjectContext:self.managedObjectContext]; // <<<<< hier crash
    15. }
    Alles anzeigen


    gräbt er sich in der letzten Zeile ein. Die resetManagedObjectContext-Methode setzt den Context für den RootViewController sowie alle Subviews:

    Quellcode

    1. -(void) resetManagedObjectContext:(NSManagedObjectContext*)to {
    2. self.managedObjectContext = to; // <<<<< hier crash
    3. if (self.addPage)
    4. {
    5. self.addPage.managedObjectContext = to;
    6. if (self.addPage.datePicker)
    7. self.addPage.datePicker.managedObjectContext= to;
    8. }
    9. if (self.overview)
    10. {
    11. self.overview.managedObjectContext = to;
    12. if (to)
    13. [self.overview resetFetchController];
    14. }
    15. if (self.calendarView)
    16. self.calendarView.managedObjectContext = to;
    17. if (self.settingsPage && self.settingsPage.categories)
    18. self.settingsPage.categories.managedObjectContext= to;
    19. }
    Alles anzeigen


    Quellcode

    1. Program received signal: “EXC_BAD_ACCESS”.
    2. (gdb) bt
    3. #0 0x02963907 in objc_msgSend ()
    4. #1 0x00000000 in ?? ()


    Ich gebe offen zu, dass ich völliger Objective-C Amateur bin, daher könnt ihr mir ruhig auf die Finger klopfen, wenn ich hier was total verkehrt mache...
  • Quellcode

    1. [managedObjectContext release];
    ist bei einer Property ganz böse, weil Du das Objekt freigibst aber den Zeiger nicht auf nil setzt. Eine spätere Zuweisung an die Property schickt dann erstmal ein release an das bereits freigegebene Objekt. Verwende stattdessen lieber

    Quellcode

    1. self.managedObjectContext = nil;
    Damit vermeidest Du den Dangling Pointer.
    „Meine Komplikation hatte eine Komplikation.“
  • Also wenn ich das richtig sehe, ist in der AppDelegate-Klasse managedObjectContext keine Property. Es gibt lediglich eine Getter-Methode. D.h. das ganze sollte nichts machen, und sobald die letzte Referenz weg ist, sollte der Context freigegeben werden, korrekt?

    Ich habe mittlerweile aber den Fehler gefunden. Der managedObjectContext wird in der Getter-Methode nur neu erzeugt, wenn er nil ist. Das hat gefehlt:

    Brainfuck-Quellcode

    1. [self.rootViewController resetManagedObjectContext:nil];
    2. [managedObjectContext release];
    3. managedObjectContext = nil; // <<<<<<<<<<


    Nun kann ich die Datenbank leeren. Jetzt bin ich allerdings da, wo ich schonmal war. Wenn ich nach dem Löschen versuche, einen neuen Record einzufügen, krachts:

    Quellcode

    1. Program received signal: “EXC_BAD_ACCESS”.
    2. (gdb) bt
    3. #0 0x02963903 in objc_msgSend ()
    4. #1 0xbfffcdd0 in ?? ()
    5. #2 0x024bfa0c in -[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] ()
    6. #3 0x0007c0e1 in _nsnote_callback ()
    7. #4 0x027def29 in __CFXNotificationPost_old ()
    8. #5 0x0275e26a in _CFXNotificationPostNotification ()
    9. #6 0x00071c8a in -[NSNotificationCenter postNotificationName:object:userInfo:] ()
    10. #7 0x023fa879 in -[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:] ()
    11. #8 0x0246a193 in -[NSManagedObjectContext(_NSInternalChangeProcessing) _createAndPostChangeNotification:withDeletions:withUpdates:withRefreshes:] ()
    12. #9 0x023dd208 in -[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] ()
    13. #10 0x02416e55 in -[NSManagedObjectContext save:] ()
    14. #11 0x00013a52 in -[MySubController saveRecord] (self=0x72359e0, _cmd=0x2f550)
    15. #12 0x003127f8 in -[UIApplication sendAction:to:from:forEvent:] ()
    16. #13 0x0051e68b in -[UIBarButtonItem(UIInternal) _sendAction:withEvent:] ()
    17. #14 0x003127f8 in -[UIApplication sendAction:to:from:forEvent:] ()
    18. #15 0x0039dde0 in -[UIControl sendAction:to:forEvent:] ()
    19. #16 0x003a0262 in -[UIControl(Internal) _sendActionsForEvents:withEvent:] ()
    20. #17 0x0039ee0f in -[UIControl touchesEnded:withEvent:] ()
    21. #18 0x003363d0 in -[UIWindow _sendTouchesForEvent:] ()
    22. #19 0x00317cb4 in -[UIApplication sendEvent:] ()
    23. #20 0x0031c9bf in _UIApplicationHandleEvent ()
    Alles anzeigen


    Ich hab mal die Pointer verglichen, das hier verwendete NSManagedObjectContext Objekt ist auf jeden fall das neue, in wipeData erzeugte Objekt, und nicht das bereits gelöschte, alte Objekt.
  • Puh, ich weiß nicht, ich hoffe nicht. Ich schmeiss auch alle Sub-Views weg, und erzeuge sie neu. Eigentlich sollte die App danach total jungfräulich sein. Wie kann ich das rausfinden, ob das Objekt noch irgendwo benutzt wird? Statt release mal dealloc?

    [edit] das hat auch nix gebracht, kommt aufs gleiche raus.
  • s710 schrieb:

    Puh, ich weiß nicht, ich hoffe nicht. Ich schmeiss auch alle Sub-Views weg, und erzeuge sie neu. Eigentlich sollte die App danach total jungfräulich sein.
    Anscheinend ist sie das nicht. Du darfst übrigens auch keine Entitäten aus dem alten Store mehr verwenden, da diese auf Ihren Objektkontext verweisen.

    s710 schrieb:

    Wie kann ich das rausfinden, ob das Objekt noch irgendwo benutzt wird?
    Wahrscheinlich gibt es dafür nur einen Weg: Sauberes, systematisches Arbeiten.

    Du kannst ja vor der Zerstörung des Stores eine Notification schicken, so dass alle noch schnell ihre Objektkontexte und Entitäten freigeben können.

    s710 schrieb:

    Statt release mal dealloc?
    Unsystematisches Raten bringt nicht viel. In welchen Situationen darfst Du denn dealloc direkt aufrufen?
    „Meine Komplikation hatte eine Komplikation.“
  • macmoonshine schrieb:

    s710 schrieb:

    Puh, ich weiß nicht, ich hoffe nicht. Ich schmeiss auch alle Sub-Views weg, und erzeuge sie neu. Eigentlich sollte die App danach total jungfräulich sein.
    Anscheinend ist sie das nicht. Du darfst übrigens auch keine Entitäten aus dem alten Store mehr verwenden, da diese auf Ihren Objektkontext verweisen.


    Das ist mir bewusst. Die Entitäten werden auch nur bei Bedarf geholt, und eigentlich nicht zwischengespeichert. Ich geh mal auf die Suche...

    macmoonshine schrieb:


    s710 schrieb:

    Wie kann ich das rausfinden, ob das Objekt noch irgendwo benutzt wird?
    Wahrscheinlich gibt es dafür nur einen Weg: Sauberes, systematisches Arbeiten.
    Du kannst ja vor der Zerstörung des Stores eine Notification schicken, so dass alle noch schnell ihre Objektkontexte und Entitäten freigeben können.


    Ich geb mein Bestes :D
    Die reset-Methode macht ja im Prinzip genau das.

    macmoonshine schrieb:


    s710 schrieb:

    Statt release mal dealloc?
    Unsystematisches Raten bringt nicht viel. In welchen Situationen darfst Du denn dealloc direkt aufrufen?


    Ich sprach von der Variable managedObjectContext des AppDelegates. Nach meinem Verständnis wird der retain-Counter erhöht, wenn ich selbiges meinen SubViews per Property zuweise. Wenn ich die Properties der SubViews wieder auf nil setze, wird dem managedObjectContext eine release-Message geschickt. Sobald der retain-Counter auf 0 steht, bekommt er ein dealloc.
    Mit dem direkten dealloc wollte ich provozieren, dass die App da Crasht, wo das Objekt noch in Benutzung ist (d.h. ich dachte es wird eventuell nicht überall per release losgelassen, so dass das Objekt nie zerstört wird).