Core Data Multi-Threating Verständnis

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

  • Core Data Multi-Threating Verständnis

    Guten Abend zusammen,

    ich habe im Moment ein Problem mit Core Data und Multi-Threading,
    was die App in unregelmäßigen Abständen abstürzen lässt.

    Erstmal zum Aufbau der App:
    - Suchmaske -> Ergebnisliste -> Detailseite
    - Merkzettel

    In der Ergebnisliste werden per NSUrlSession die Daten anhand der eingestellten Suchmaske vom Server geladen. Im CompletionBlock werden die geladenen Daten (JSON) in CoreData gespeichert. Wenn ein geladenes Objekt schon in CoreData ist (da zB auf dem Merkzettel) wird dieses Objekt verwendet, ansonsten wird ein neues ManagedObject erstellt.
    So ein ManagedObject hat mehrere 1:1 und 1:m Relations.

    In der Detailseite werden die kompletten Daten zu dem dem in der Ergebnisliste ausgewählten ManagedObject geladen und im CompletionBlock wird das ManagedObject mit den restlichen Daten gefüllt.

    Die Objekte können auf den Merkzettel gelegt werden, dieser soll auch offline verfügbar sein. Daher die Speicherung in CoreData.

    Ergebnisliste und Detailseite greifen auf das gleiche Array mit den geladenen Objekten zu.

    Ich habe jetzt MagicalRecord eingebaut.

    In der Ergebnisliste werden die NSManagedObjects im contextForCurrentThread erstellt und danach mit saveToPersistantStoreAndWait gespeichert.
    In der Detailseite werden die restlichen Relationships im managedObject.managedObjectContext erstellt und wiederrum mit saveToPersistantStoreAndWait in die DB gespeichert.

    1.
    Dadurch dass die Daten async geladen werden und daher im CompletionBlock die ManagedObjects im Background Thread erstellt werden, bekommen diese ManagedObjects auch den Background Context als ManagedObjectContext.
    Mit saveToPersistantStoreAndWait werden die Daten in den Main Context gemerged und in die DB gespeichert. Der managedObject.managedObjectContext bleibt aber weiterhin der Background Context?

    2.
    Wenn ich nun in der Ergebnisliste oder in der Detailseite ein Objekt auf den Merkzettel lege, wird die entsprechende Property auf YES gesetzt. Muss ich dann nach jeder Property Änderung wiederum saveToPersistantStoreAndWait aufrufen? Ansonsten wird diese Änderung wohl nicht in die DB gespeichert?

    3.
    Im Merkzettel mache ich in viewDidLoad einen FetchRequest auf alle Objekte in der DB, welche die Merkzettel Property auf YES haben. Dies geschieht ja dann im Main Thread = Main Context.
    Somit haben die Objecte als ManagedObjectContext den Main Context (welcher in der Ergebnisliste/Detailseite noch der Background Context war).
    Wenn ich nun im Merkzettel die Daten eines Objektes vom Server aktualisiere, dann passiert dies ja wieder async im Background Thread (analog zur Detailseite), das Objekt ist aber im Main Context. Dies müsste ja dann auch zu einem Crash führen, da unterschiedliche Contexte. Dann müsste ich wohl das Objekt im Background Context neu fetchen, bearbeiten, speichern. und im Merkzettel den Fetch aller Objekte neu ausführen?

    Das sind gerade die ersten Unklarheiten. Ist irgendwie alles eine Wissenschaft für sich^^
    Ggf bin ich auch komplett falsch vom Ansatz.

    vg
    Knowing is not enough, we must apply.
    Willing is not enough, we must do.
  • Ich habe auch schon Tage und Wochen damit verbracht CoraData und und Multitasking unter einen Hut zu bringen. Selbst Gespräche auf der macoun haben immer darin geendet, dass keiner eine so 100% zufrieden stellende Lösung finden konnte. Ich habe mich damit abgefunden das ich das nicht vernünftig ans Laufen bekomme und mache nun alle CoreData Schreibzugriffe immer mit PerformSelectorInMainThread. Dadurch habe ich eventuellen etwas Performance eingebüsst, dafür aber keine Abstürze.

    Gruss

    Claus
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)
  • ja dashab ich beim recherchieren auch gemerkt, dass jeder es irgendwie anders macht :-/
    von 3 verschiedenen ManagedObjectContexts (mainContext, writerContext, tempContext) bis hin zur Überprüfung des aktuellen Threads am Anfang der Methode

    Quellcode

    1. if (![NSThread isMainThread]) {dispatch_async(dispatch_get_main_queue { ruf mich selbst nochmal auf im MainThread})


    Also es ist schon weng zum verzweifeln, wenn es sich bei jeder App Nutzung anders verhält bzw sie willkürlich mal crashed und mal nicht :(
    Knowing is not enough, we must apply.
    Willing is not enough, we must do.
  • Wenn ich die Guides richtig verstehe, ist CoreData mit Multithreading alles Andere als trivial.
    Unter Anderem deshalb dürfte es keine 100% zufriedenstellende Lösung geben.

    In Deinem Fall brauchst Du die Objekte ja nur in CoreData, wenn jemand auf 'vormerken' tippt.
    Wäre es nicht sinnvoll, in dem Zusammenhang aus dem angezeigten Objekt der Detailseite erst auf Knopfdruck ein ManagedObject zu erzeugen und zu persistieren?
    Du hättest dann wenige schreibende Zugriffe auf CoreData, die sich im Mainthread durchführen lassen.

    Momentan machst Du ja irgendwie alle Objekte offline verfügbar, zeigst aber nicht alle offline zur Verfügung stehenden Objekte an – das wirkt auf mich nicht unbedingt einleuchtend und daher wie ein mögliches Designproblem.

    Wenn Du die gesammelten und konvertierten (non-CoreData) Objekte in einem atomic immutable Container vor hältst, sollten auch Abstürze durch Zugriff aus unterschiedlichen Threads vermieden werden können.Insgesamt ist die Interaktion von Objekten/Instanzvariablen über mehrere Threads hinweg alles Andere als trivial. Versuch das Ganze mal ohne Core Data zu lösen, dann wirst Du verstehen, warum das so ein Voodoo ist.


    PS: Es ist übrigens ein altbekannter Seiteneffekt von Multithreading, dass es sich bei jeder App Nutzung anders verhält bzw. sie willkürlich mal crashed und mal nicht.
    Unter Anderem darauf wies der Frank Illenberger in seinem Talk 'Threads sind böse™' auf der Macoun 2014 hin. ;)
    «Applejack» "Don't you use your fancy mathematics to muddle the issue!"

    Iä-86! Iä-64! Awavauatsh fthagn!

    kmr schrieb:

    Ach, Du bist auch so ein leichtgläubiger Zeitgenosse, der alles glaubt, was irgendwelche Typen vor sich hin brabbeln. :-P
  • Das Grundproblem bei Core Data und Multithreading ist, dass es dafür eigentlich nie gedacht war. Wir erinnern uns an 10.4 (!) und den Frühling 2005 (als es noch richtig Sommer wurde, Rudi Carrell) zurück. Eine CPU – G5 – war im iMac verbaut und eine CPU bedeutete einen Core. Die Anwendungsprogramme waren Dokumenten orientiert.

    Für diese Welt, nur noch den Älteren unter uns bekannt ("Damals hatten wir ja noch nichts"), wurde Core Data gedacht. Da wurden Datenbanken als Dokument geöffnet, gehörten der Applikation, wurden Stunden später gespeichert und irgendwann geschlossen. Dokumente statt Transaktionen. Ewige Durchführbarkeit von Operationen statt Commit-Fehler. Klingt so gar nicht nach Datenbank. Klingt auch so gar nicht nach Multithreading.

    Man muss sich klar machen, wo das Problem liegt, um das in den Griff zu bekommen. Also: Core Data lädt Objekte in einen Kontext. Dabei werden die Objekte jedoch nicht vollständig geladen (was übrigens auch nur das Problem verlagern würde), sondern als Fault, sozusagen als Eintrag auf der Einkaufsliste. In einem anderen Thread geschieht dies auch. Hier werden aber mal spaßeshalber Objekte gelöscht und zwar die, die sich im ersten Thread und Context im Fault befanden. Das ganze wird auch noch bis hoch zur Platte durchpersistiert. In dem ersten Kontext wird jetzt auf etwas im Objekt zugegriffen, welches gerade noch im Fault lag und Core Data geht darauf mal hin zum örtlichen SQL-Store, um das Ding dann auch mal zu holen.

    UPS, DAS REGAL IST LEER!

    Wie man das hinbekommt? Aufpassen wie Sau!

    Habe ich eine Liste mit 10 Einträgen und mache mit einem davon etwas ewig Langes in einem anderen Thread, so muss ich eben dafür Sorge tragen, dass das Ding im Main-Thread nicht aus der Liste entfernt wird. Aber eigentlich muss ich das in vielen Fällen ohnehin machen, weil sonst die Operation semantisch keinen Sinn ergibt. Aber es würde nicht crashen. (Oder doch, kommt drauf an.) Man muss sich also nachhalten, wo welche Objekte gerade herumschwirren. Nicht einfach, aber wenn man es kapiert hat, kann man das sogar generisch halten. (Gut, mit Swift würde das problematisch, aber das spielt keine Rolle, weil man ohnehin 3 Jahre bräuchte um das zu programmieren (keine brauchbare Code-Completion, Turn-Arounds von Tagen, Compilerfehler usw. usf.) Was soll's?)

    Übrigens ist der Treppenwitz dabei, dass DBMS so etwas eigentlich gut können, weil Amazon halt nicht jedesmal abstürzen soll, wenn 1000 Kunden gleichzeitig einen Artikel bestellen, während er aus dem Programm genommen wird. Die Leute, die DBMS programmieren sind sogar richtige Experten dafür, die kennen sich gut aus. Aber die sagen eben auch einfach mal: "Operation geht nicht mehr", wenn man eine Transaktion comittet. Aber es gibt ja keine Transaktionen. Dafür den Rat, nach jeder Nutzeroperation zu speichern, um sich das so anfühlen zu lassen. Liebes Apple, häufig dasselbe Dokument zu speichern, sind nicht viele Transaktionen, echt nicht.
    Es hat noch nie etwas gefunzt. To tear down the Wall would be a Werror!
    25.06.2016: [Swift] gehört zu meinen *Favorite Tags* auf SO. In welcher Bedeutung von "favorite"?