Best Practice für den Hintergrund des Kerndatenhintergrunds

79

Ich habe eine große Importaufgabe, die ich mit Kerndaten erledigen muss.
Angenommen, mein Kerndatenmodell sieht folgendermaßen aus:

Car
----
identifier 
type

Ich rufe eine Liste mit JSON für Fahrzeuginformationen von meinem Server ab und möchte sie dann mit meinem Kerndatenobjekt synchronisieren. CarDies bedeutet:
Wenn es sich um ein neues Auto handelt -> erstellen Sie Caraus den neuen Informationen ein neues Kerndatenobjekt .
Wenn das Auto bereits vorhanden ist -> aktualisieren Sie das Core Data- CarObjekt.

Daher möchte ich diesen Import im Hintergrund durchführen, ohne die Benutzeroberfläche zu blockieren, und während der Verwendung wird eine Tabellenansicht für Autos gescrollt, in der alle Autos angezeigt werden.

Momentan mache ich so etwas:

// create background context
NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[bgContext setParentContext:self.mainContext];

[bgContext performBlock:^{
    NSArray *newCarsInfo = [self fetchNewCarInfoFromServer]; 

    // import the new data to Core Data...
    // I'm trying to do an efficient import here,
    // with few fetches as I can, and in batches
    for (... num of batches ...) {

        // do batch import...

        // save bg context in the end of each batch
        [bgContext save:&error];
    }

    // when all import batches are over I call save on the main context

    // save
    NSError *error = nil;
    [self.mainContext save:&error];
}];

Aber ich bin mir nicht sicher, ob ich hier das Richtige tue, zum Beispiel:

Ist es in Ordnung, dass ich benutze setParentContext?
Ich habe einige Beispiele gesehen, die es so verwenden, aber ich habe andere Beispiele gesehen, die nicht aufrufen setParentContext, stattdessen machen sie so etwas:

NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
bgContext.persistentStoreCoordinator = self.mainContext.persistentStoreCoordinator;  
bgContext.undoManager = nil;

Eine andere Sache, bei der ich mir nicht sicher bin, ist, wann ich save im Hauptkontext aufrufen soll. In meinem Beispiel rufe ich save am Ende des Imports auf, aber ich habe Beispiele gesehen, die Folgendes verwenden:

[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) {
    NSManagedObjectContext *moc = self.managedObjectContext;
    if (note.object != moc) {
        [moc performBlock:^(){
            [moc mergeChangesFromContextDidSaveNotification:note];
        }];
    }
}];  

Wie ich bereits erwähnt habe, möchte ich, dass der Benutzer während der Aktualisierung mit den Daten interagieren kann. Was ist also sicher, wenn der Benutzer einen Fahrzeugtyp ändert, während der Import dasselbe Fahrzeug ändert?

AKTUALISIEREN:

Dank der großartigen Erklärung von @TheBasicMind versuche ich, Option A zu implementieren, sodass mein Code ungefähr so ​​aussieht:

Dies ist die Kerndatenkonfiguration in AppDelegate:

AppDelegate.m  

#pragma mark - Core Data stack

- (void)saveContext {
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
            DDLogError(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}  

// main
- (NSManagedObjectContext *)managedObjectContext {
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }

    _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    _managedObjectContext.parentContext = [self saveManagedObjectContext];

    return _managedObjectContext;
}

// save context, parent of main context
- (NSManagedObjectContext *)saveManagedObjectContext {
    if (_writerManagedObjectContext != nil) {
        return _writerManagedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _writerManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_writerManagedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _writerManagedObjectContext;
}  

Und so sieht meine Importmethode jetzt aus:

- (void)import {
    NSManagedObjectContext *saveObjectContext = [AppDelegate saveManagedObjectContext];

    // create background context
    NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    bgContext.parentContext = saveObjectContext;

    [bgContext performBlock:^{
        NSArray *newCarsInfo = [self fetchNewCarInfoFromServer];

        // import the new data to Core Data...
        // I'm trying to do an efficient import here,
        // with few fetches as I can, and in batches
        for (... num of batches ...) {

            // do batch import...

            // save bg context in the end of each batch
            [bgContext save:&error];
        }

        // no call here for main save...
        // instead use NSManagedObjectContextDidSaveNotification to merge changes
    }];
}  

Und ich habe auch folgenden Beobachter:

[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) {

    NSManagedObjectContext *mainContext = self.managedObjectContext;
    NSManagedObjectContext *otherMoc = note.object;

    if (otherMoc.persistentStoreCoordinator == mainContext.persistentStoreCoordinator) {
        if (otherMoc != mainContext) {
            [mainContext performBlock:^(){
                [mainContext mergeChangesFromContextDidSaveNotification:note];
            }];
        }
    }
}];
Eyal
quelle
Wenn Sie das Eltern-Kind-Modell verwenden, müssen Sie das Kind und dann das Elternteil speichern, damit die Datenbank korrekt aktualisiert wird.
Carloabelli
Dies ist Teil meiner Frage: Soll ich für diese Art von Aufgabe ein Eltern-Kind-Modell verwenden? und was sind meine anderen Möglichkeiten?
Eyal
1
Sie sollten diesen Beitrag besuchen, Sie werden es hilfreich finden raywenderlich.com/15916/…
Julio Montoya
1
@ cabellicar123 warum? Aus dem Core Data Programming Guide: Parallelität: Once all of the data has been consumed and turned into NSManagedObject instances, you call save on the private context, which moves all of the changes into the main queue context without blocking the main queue. developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
Dima Deplov
@Eyal "Ich versuche hier einen effizienten Import durchzuführen" Wie ist das?
Sagte

Antworten:

187

Dies ist ein äußerst verwirrendes Thema für Personen, die sich zum ersten Mal an Core Data wenden. Ich sage das nicht leichtfertig, aber mit der Erfahrung bin ich zuversichtlich, dass die Apple-Dokumentation in dieser Angelegenheit etwas irreführend ist (es ist in der Tat konsistent, wenn Sie es sehr sorgfältig lesen, aber sie veranschaulichen nicht angemessen, warum das Zusammenführen von Daten bestehen bleibt In vielen Fällen ist dies eine bessere Lösung, als sich auf Eltern / Kind-Kontexte zu verlassen und einfach von einem Kind zum Elternteil zu speichern.

Die Dokumentation vermittelt den starken Eindruck, dass Eltern / Kind-Kontexte die neue bevorzugte Methode für die Hintergrundverarbeitung sind. Apple vernachlässigt es jedoch, einige starke Vorbehalte hervorzuheben. Beachten Sie zunächst, dass alles, was Sie in den Kontext Ihres Kindes holen, zuerst über das übergeordnete Element abgerufen wird. Daher ist es am besten, jedes untergeordnete Element des Hauptkontexts, das auf dem Hauptthread ausgeführt wird, auf die Verarbeitung (Bearbeitung) von Daten zu beschränken, die bereits in der Benutzeroberfläche des Hauptthreads angezeigt wurden. Wenn Sie es für allgemeine Synchronisierungsaufgaben verwenden, möchten Sie wahrscheinlich Daten verarbeiten, die weit über die Grenzen dessen hinausgehen, was Sie derzeit in der Benutzeroberfläche anzeigen. Selbst wenn Sie NSPrivateQueueConcurrencyType für den untergeordneten Bearbeitungskontext verwenden, ziehen Sie möglicherweise eine große Datenmenge durch den Hauptkontext, was zu schlechter Leistung und Blockierung führen kann. Jetzt ist es am besten, den Hauptkontext nicht zu einem untergeordneten Element des Kontexts zu machen, den Sie für die Synchronisierung verwenden, da er nicht über Synchronisierungsaktualisierungen benachrichtigt wird, es sei denn, Sie führen dies manuell aus, und Sie führen möglicherweise lang laufende Aufgaben auf einem aus Kontext müssen Sie möglicherweise auf Speicherungen reagieren, die als Kaskade vom Bearbeitungskontext, der Ihrem Hauptkontext untergeordnet ist, über den Hauptkontakt bis hinunter zum Datenspeicher initiiert wurden. Sie müssen entweder die Daten manuell zusammenführen und möglicherweise auch verfolgen, was im Hauptkontext ungültig gemacht und erneut synchronisiert werden muss. Nicht das einfachste Muster. Außerdem führen Sie potenziell lange laufende Aufgaben in einem Kontext aus, in dem Sie möglicherweise auf Speicherungen reagieren müssen, die als Kaskade vom Bearbeitungskontext, der Ihrem Hauptkontext untergeordnet ist, über den Hauptkontakt bis zum Datenspeicher initiiert wurden. Sie müssen entweder die Daten manuell zusammenführen und möglicherweise auch verfolgen, was im Hauptkontext ungültig gemacht werden muss, und erneut synchronisieren. Nicht das einfachste Muster. Außerdem führen Sie potenziell lange laufende Aufgaben in einem Kontext aus, in dem Sie möglicherweise auf Speicherungen reagieren müssen, die als Kaskade vom Bearbeitungskontext, der Ihrem Hauptkontext untergeordnet ist, über den Hauptkontakt bis zum Datenspeicher initiiert wurden. Sie müssen entweder die Daten manuell zusammenführen und möglicherweise auch verfolgen, was im Hauptkontext ungültig gemacht werden muss, und erneut synchronisieren. Nicht das einfachste Muster.

In der Apple-Dokumentation wird nicht klargestellt, dass Sie höchstwahrscheinlich eine Mischung aus den auf den Seiten beschriebenen Techniken benötigen, die die "alte" Vorgehensweise beim Einschließen von Threads und die neue Vorgehensweise bei Eltern-Kind-Kontexten beschreiben.

Ihre beste Wahl ist wahrscheinlich (und ich gebe hier eine generische Lösung, die beste Lösung hängt möglicherweise von Ihren detaillierten Anforderungen ab), einen NSPrivateQueueConcurrencyType-Speicherkontext als oberstes übergeordnetes Element zu haben, der direkt im Datenspeicher gespeichert wird. [Bearbeiten: Sie werden in diesem Kontext nicht viel direkt tun], geben Sie diesem gespeicherten Kontext mindestens zwei direkte untergeordnete Elemente. Einer Ihrer NSMainQueueConcurrencyType-Hauptkontexte, die Sie für die Benutzeroberfläche verwenden [Bearbeiten: Es ist am besten, diszipliniert zu sein und zu vermeiden, dass die Daten in diesem Kontext jemals bearbeitet werden], der andere ein NSPrivateQueueConcurrencyType, mit dem Sie Benutzerdaten bearbeiten und auch (in Option A im beigefügten Diagramm) Ihre Synchronisationsaufgaben.

Anschließend machen Sie den Hauptkontext zum Ziel der vom Synchronisierungskontext generierten NSManagedObjectContextDidSave-Benachrichtigung und senden das .userInfo-Wörterbuch für Benachrichtigungen an mergeChangesFromContextDidSaveNotification: des Hauptkontexts.

Die nächste zu berücksichtigende Frage ist, wo Sie den Benutzerbearbeitungskontext ablegen (der Kontext, in dem vom Benutzer vorgenommene Änderungen wieder in die Benutzeroberfläche übernommen werden). Wenn sich die Aktionen des Benutzers immer auf Änderungen an kleinen Mengen der präsentierten Daten beschränken, ist es am besten und am einfachsten, diese mithilfe des NSPrivateQueueConcurrencyType wieder zu einem untergeordneten Element des Hauptkontexts zu machen (Speichern speichert dann Änderungen direkt im Hauptkontext und wenn Wenn Sie einen NSFetchedResultsController haben, wird die entsprechende Delegate-Methode automatisch aufgerufen, damit Ihre Benutzeroberfläche den Update-Controller verarbeiten kann: didChangeObject: atIndexPath: forChangeType: newIndexPath :) (auch dies ist Option A).

Wenn Benutzeraktionen andererseits dazu führen können, dass große Datenmengen verarbeitet werden, sollten Sie in Betracht ziehen, sie zu einem anderen Peer des Hauptkontexts und des Synchronisierungskontexts zu machen, sodass der Sicherungskontext drei direkte untergeordnete Elemente hat. main , sync (privater Warteschlangentyp) und edit (privater Warteschlangentyp). Ich habe diese Anordnung als Option B im Diagramm gezeigt.

Ähnlich wie beim Synchronisierungskontext müssen Sie [Bearbeiten: Konfigurieren des Hauptkontexts zum Empfangen von Benachrichtigungen] beim Speichern von Daten (oder wenn Sie mehr Granularität benötigen, wenn Daten aktualisiert werden) und Maßnahmen zum Zusammenführen der Daten ergreifen (normalerweise mithilfe von mergeChangesFromContextDidSaveNotification: ). Beachten Sie, dass bei dieser Anordnung der Hauptkontext niemals die Methode save: aufrufen muss. Geben Sie hier die Bildbeschreibung ein

Um die Eltern-Kind-Beziehungen zu verstehen, wählen Sie Option A: Der Eltern-Kind-Ansatz bedeutet einfach, dass wenn der Bearbeitungskontext NSManagedObjects abruft, diese zuerst in den Sicherungskontext, dann in den Hauptkontext und schließlich in den Bearbeitungskontext "kopiert" (registriert mit) werden. Sie können Änderungen daran vornehmen. Wenn Sie dann Speichern aufrufen: Im Bearbeitungskontext werden die Änderungen nur im Hauptkontext gespeichert . Sie müssten save: im Hauptkontext und dann save: im save-Kontext aufrufen, bevor sie auf die Festplatte geschrieben werden.

Wenn Sie von einem Kind bis zu einem Elternteil speichern, werden die verschiedenen Benachrichtigungen zum Ändern und Speichern von NSManagedObject ausgelöst. Wenn Sie beispielsweise einen Abrufergebnis-Controller verwenden, um Ihre Daten für Ihre Benutzeroberfläche zu verwalten, werden die Delegierungsmethoden aufgerufen, damit Sie die Benutzeroberfläche entsprechend aktualisieren können.

Einige Konsequenzen: Wenn Sie Objekt und NSManagedObject A im Bearbeitungskontext abrufen, ändern Sie es und speichern Sie es, damit die Änderungen an den Hauptkontext zurückgegeben werden. Sie haben das geänderte Objekt jetzt sowohl für den Haupt- als auch für den Bearbeitungskontext registriert. Es wäre ein schlechter Stil, dies zu tun, aber Sie könnten das Objekt jetzt im Hauptkontext erneut ändern und es unterscheidet sich jetzt von dem Objekt, da es im Bearbeitungskontext gespeichert ist. Wenn Sie dann versuchen, weitere Änderungen am im Bearbeitungskontext gespeicherten Objekt vorzunehmen, sind Ihre Änderungen nicht mit dem Objekt im Hauptkontext synchron, und jeder Versuch, den Bearbeitungskontext zu speichern, führt zu einem Fehler.

Aus diesem Grund ist es bei einer Anordnung wie Option A ein gutes Muster, zu versuchen, Objekte abzurufen, zu ändern, zu speichern und den Bearbeitungskontext zurückzusetzen (z. B. [editContext reset] mit einer einzelnen Iteration der Run-Schleife (oder innerhalb) jeder gegebenen Block übergeben [EditContext performBlock:]) . Es ist auch am besten , diszipliniert zu sein und vermeiden Sie jemals tun alle . auf dem Hauptkontext Änderungen auch , erneut auf , da die gesamte Verarbeitung auf Haupt der Haupt - Thread ist, wenn Sie holen Viele Objekte im Bearbeitungskontext, der Hauptkontext führt die Abrufverarbeitung im Hauptthread durchda diese Objekte iterativ von übergeordneten zu untergeordneten Kontexten kopiert werden. Wenn viele Daten verarbeitet werden, kann dies dazu führen, dass die Benutzeroberfläche nicht mehr reagiert. Wenn Sie beispielsweise über einen großen Speicher verwalteter Objekte verfügen und über eine UI-Option verfügen, die dazu führt, dass alle Objekte bearbeitet werden. In diesem Fall wäre es eine schlechte Idee, Ihre App wie Option A zu konfigurieren. In einem solchen Fall ist Option B die bessere Wahl.

Wenn Sie nicht Tausende von Objekten verarbeiten, ist Option A möglicherweise völlig ausreichend.

Übrigens, machen Sie sich keine Sorgen darüber, welche Option Sie auswählen. Es könnte eine gute Idee sein, mit A zu beginnen und wenn Sie zu B wechseln müssen. Es ist einfacher als Sie vielleicht denken, eine solche Änderung vorzunehmen, und hat normalerweise weniger Konsequenzen als Sie vielleicht erwarten.

TheBasicMind
quelle
Vielen Dank (und +1) für Ihre ausführliche Antwort. An diesem Punkt meiner Entwicklung wird es eine große Veränderung sein, diese zusätzlichen Kontexte hinzuzufügen. Ich möchte wirklich nur einen zusätzlichen Kontext verwenden, um diese Hintergrundaufgabe zu erledigen und sie so einfach wie möglich zu halten. Ich verstehe immer noch nicht den Unterschied zwischen dem Eltern-Kind-Ansatz und den anderen Alternativen. Ich weiß es zu schätzen, wenn Sie meinen Beispielcode verwenden und falsche Schritte korrigieren können, die ich dort ausgeführt habe. Das ist für mich leichter zu verstehen und zu tun.
Eyal
Wählen Sie Option A. Der übergeordnete untergeordnete Ansatz bedeutet einfach, dass, wenn der Bearbeitungskontext NSManagedObjects abruft, diese zuerst in den Sicherungskontext, dann in den Hauptkontext und schließlich in den Bearbeitungskontext "kopiert" (registriert mit) werden. Sie können Änderungen daran vornehmen. Wenn Sie dann Speichern aufrufen: Im Bearbeitungskontext werden die Änderungen nur im Hauptkontext gespeichert . Sie müssten save: im Hauptkontext aufrufen und dann save: beim save aufrufen, bevor sie auf die Festplatte geschrieben werden. Ich werde meine Antwort ändern.
TheBasicMind
3
Ich bin so froh, dass Sie sich entschieden haben, hier zu antworten. Ihre Informationen sind von unschätzbarem Wert. Ich glaube, ich habe jetzt mehr Selbstvertrauen, um das auszuprobieren. Ich muss nur ein bisschen Ordnung in all diese tollen Informationen in meinem Kopf bringen. Der Bearbeitungskontext verwirrt mich ein wenig. Was ist der Unterschied zwischen ihm und einem anderen Hintergrundkontext? Warum ist er ein Kind und der Synchronisierungskontext nicht? Im Moment denke ich, dass ich keinen separaten Kontext verwenden werde, um Benutzeränderungen vorzunehmen, da der Benutzer jeweils nur ein Element (Auto) löschen oder eine Eigenschaft einzeln ändern kann. Daher führe ich jede Bearbeitung mit dem Hauptkontext durch und rufe direkt auf speichern: drauf.
Eyal
Ich habe meine Frage auch mit neuem Code aktualisiert, den ich geschrieben habe, um Option A zu implementieren (abzüglich des untergeordneten Kontextes Bearbeiten). Kannst du bitte überprüfen, ob es in Ordnung ist? Ich bin nicht sicher, ob ich save aufrufen muss: im Save-Kontext irgendwo? oder reicht es aus, die Änderungen in der Benachrichtigung zusammenzuführen?
Eyal
@TheBasicMind Wann ist die Zeit zum Speichern des "Speichern" -Kontexts? Tun Sie dies unabhängig von der Benutzerinteraktion?
Ctietze
14

Erstens ist der Eltern / Kind-Kontext nicht für die Hintergrundverarbeitung vorgesehen. Sie dienen zur atomaren Aktualisierung verwandter Daten, die möglicherweise in mehreren Ansichtscontrollern erstellt werden. Wenn der letzte Ansichtscontroller abgebrochen wird, kann der untergeordnete Kontext ohne nachteilige Auswirkungen auf den übergeordneten Kontext verworfen werden. Dies wird von Apple am Ende dieser Antwort unter [^ 1] vollständig erklärt. Nun, da dies nicht im Weg ist und Sie nicht auf den häufigen Fehler hereingefallen sind, können Sie sich darauf konzentrieren, wie Sie die Kerndaten im Hintergrund richtig verarbeiten.

Erstellen Sie einen neuen persistenten Speicherkoordinator (unter iOS 10 nicht mehr erforderlich, siehe Update unten) und einen privaten Warteschlangenkontext. Warten Sie auf die Benachrichtigung zum Speichern und führen Sie die Änderungen in den Hauptkontext ein (unter iOS 10 verfügt der Kontext über eine Eigenschaft, die dies automatisch ausführt).

Ein Beispiel von Apple finden Sie unter "Erdbeben: Auffüllen eines Kerndatenspeichers mithilfe einer Hintergrundwarteschlange" https://developer.apple.com/library/mac/samplecode/Earthquakes/Introduction/Intro.html Wie Sie dem Revisionsverlauf entnehmen können Am 19.08.2014 fügten sie hinzu: "Neuer Beispielcode, der zeigt, wie ein zweiter Kerndatenstapel zum Abrufen von Daten in einer Hintergrundwarteschlange verwendet wird."

Hier ist das Bit von AAPLCoreDataStackManager.m:

// Creates a new Core Data stack and returns a managed object context associated with a private queue.
- (NSManagedObjectContext *)createPrivateQueueContext:(NSError * __autoreleasing *)error {

    // It uses the same store and model, but a new persistent store coordinator and context.
    NSPersistentStoreCoordinator *localCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[AAPLCoreDataStackManager sharedManager].managedObjectModel];

    if (![localCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil
                                                  URL:[AAPLCoreDataStackManager sharedManager].storeURL
                                              options:nil
                                                error:error]) {
        return nil;
    }

    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [context performBlockAndWait:^{
        [context setPersistentStoreCoordinator:localCoordinator];

        // Avoid using default merge policy in multi-threading environment:
        // when we delete (and save) a record in one context,
        // and try to save edits on the same record in the other context before merging the changes,
        // an exception will be thrown because Core Data by default uses NSErrorMergePolicy.
        // Setting a reasonable mergePolicy is a good practice to avoid that kind of exception.
        context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;

        // In OS X, a context provides an undo manager by default
        // Disable it for performance benefit
        context.undoManager = nil;
    }];
    return context;
}

Und in AAPLQuakesViewController.m

- (void)contextDidSaveNotificationHandler:(NSNotification *)notification {

    if (notification.object != self.managedObjectContext) {

        [self.managedObjectContext performBlock:^{
            [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
        }];
    }
}

Hier ist die vollständige Beschreibung des Aufbaus des Beispiels:

Erdbeben: Verwenden eines "privaten" persistenten Speicherkoordinators zum Abrufen von Daten im Hintergrund

Die meisten Anwendungen, die Core Data verwenden, verwenden einen einzelnen Koordinator für persistente Speicher, um den Zugriff auf einen bestimmten persistenten Speicher zu vermitteln. Erdbeben zeigt, wie ein zusätzlicher "privater" persistenter Speicherkoordinator verwendet wird, wenn verwaltete Objekte mit Daten erstellt werden, die von einem Remoteserver abgerufen wurden.

Anwendungsarchitektur

Die Anwendung verwendet zwei Core Data "Stacks" (wie durch die Existenz eines persistenten Speicherkoordinators definiert). Der erste ist der typische "Allzweck" -Stapel; Der zweite wird von einem Ansichts-Controller speziell zum Abrufen von Daten von einem Remote-Server erstellt (ab iOS 10 wird kein zweiter Koordinator mehr benötigt, siehe Update unten in der Antwort).

Der wichtigste persistente Speicherkoordinator wird von einem einzelnen "Stack Controller" -Objekt (einer Instanz von CoreDataStackManager) verkauft. Es liegt in der Verantwortung seiner Kunden, einen verwalteten Objektkontext für die Zusammenarbeit mit dem Koordinator zu erstellen [^ 1]. Der Stack-Controller verkauft auch Eigenschaften für das von der Anwendung verwendete verwaltete Objektmodell und den Speicherort des persistenten Speichers. Clients können diese letzteren Eigenschaften verwenden, um zusätzliche persistente Speicherkoordinatoren einzurichten, die parallel zum Hauptkoordinator arbeiten.

Der Hauptansichts-Controller, eine Instanz von QuakesViewController, verwendet den persistenten Speicherkoordinator des Stapelcontrollers, um Beben aus dem persistenten Speicher abzurufen und in einer Tabellenansicht anzuzeigen. Das Abrufen von Daten vom Server kann ein langwieriger Vorgang sein, der eine erhebliche Interaktion mit dem persistenten Speicher erfordert, um festzustellen, ob es sich bei den vom Server abgerufenen Datensätzen um neue Beben oder um potenzielle Aktualisierungen vorhandener Beben handelt. Um sicherzustellen, dass die Anwendung während dieses Vorgangs reaktionsfähig bleibt, verwendet der Ansichtscontroller einen zweiten Koordinator, um die Interaktion mit dem persistenten Speicher zu verwalten. Der Koordinator wird so konfiguriert, dass er dasselbe verwaltete Objektmodell und denselben persistenten Speicher verwendet wie der vom Stapelcontroller gelieferte Hauptkoordinator.

[^ 1]: Dies unterstützt den "Pass the Baton" -Ansatz, bei dem - insbesondere in iOS-Anwendungen - ein Kontext von einem View Controller an einen anderen übergeben wird. Der Root-View-Controller ist dafür verantwortlich, den anfänglichen Kontext zu erstellen und ihn bei Bedarf an untergeordnete View-Controller zu übergeben.

Der Grund für dieses Muster besteht darin, sicherzustellen, dass Änderungen am verwalteten Objektdiagramm angemessen eingeschränkt werden. Core Data unterstützt "verschachtelte" verwaltete Objektkontexte, die eine flexible Architektur ermöglichen, die es einfach macht, unabhängige, stornierbare Änderungssätze zu unterstützen. In einem untergeordneten Kontext können Sie dem Benutzer erlauben, eine Reihe von Änderungen an verwalteten Objekten vorzunehmen, die dann entweder als einzelne Transaktion im Großhandel an das übergeordnete Objekt übergeben (und letztendlich im Geschäft gespeichert) oder verworfen werden können. Wenn alle Teile der Anwendung einfach denselben Kontext beispielsweise von einem Anwendungsdelegierten abrufen, ist dieses Verhalten nur schwer oder gar nicht zu unterstützen.

Update: In iOS 10 hat Apple die Synchronisierung von der SQLite-Dateiebene auf den dauerhaften Koordinator verschoben. Dies bedeutet, dass Sie jetzt einen privaten Warteschlangenkontext erstellen und den vorhandenen Koordinator, der vom Hauptkontext verwendet wird, wiederverwenden können, ohne die gleichen Leistungsprobleme zu haben, die Sie zuvor auf diese Weise gehabt hätten, cool!

malhal
quelle
7

Übrigens erklärt dieses Dokument von Apple dieses Problem sehr deutlich. Schnelle Version von oben für alle Interessierten

let jsonArray = … //JSON data to be imported into Core Data
let moc = … //Our primary context on the main queue

let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateMOC.parentContext = moc

privateMOC.performBlock {
    for jsonObject in jsonArray {
        let mo = … //Managed object that matches the incoming JSON structure
        //update MO with data from the dictionary
    }
    do {
        try privateMOC.save()
        moc.performBlockAndWait {
            do {
                try moc.save()
            } catch {
                fatalError("Failure to save context: \(error)")
            }
        }
    } catch {
        fatalError("Failure to save context: \(error)")
    }
}

Und noch einfacher, wenn Sie NSPersistentContainer für iOS 10 und höher verwenden

let jsonArray = …
let container = self.persistentContainer
container.performBackgroundTask() { (context) in
    for jsonObject in jsonArray {
        let mo = CarMO(context: context)
        mo.populateFromJSON(jsonObject)
    }
    do {
        try context.save()
    } catch {
        fatalError("Failure to save context: \(error)")
    }
}
Hariszaman
quelle
Wie man Daten abruft, können Sie bitte ein Beispiel dafür geben, da ich beim Abrufen zufällig abstürze und manchmal richtig arbeite. @hariszaman
Parth Barot
Es könnte sein, dass Sie versuchen, den Kontext gleichzeitig zu ändern und zu speichern
Hariszaman