5

Ich habe etwas Ineffizienz in meiner App, die ich gerne verstehen und beheben würde.Core Data Muster: Wie man lokale Informationen mit Änderungen vom Netz effizient aktualisiert?

Mein Algorithmus ist:

fetch object collection from network 
for each object: 
    if (corresponding locally stored object not found): -- A 
    create object 
    if (a nested related object locally not found): -- B 
     create a related object 

ich die Kontrolle auf den Leitungen A und B tue durch ein Prädikat Abfrage mit der entsprechenden Objektschlüssel erstellen, die Teil meines Schema ist. Ich sehe, dass sowohl A (r) und B (wenn die Ausführung in den Teil verzweigt) erzeugen eine SQL wählen wie:

2010-02-05 01:57:51.092 app[393:207] CoreData: sql: SELECT <a bunch of fields> FROM ZTABLE1 t0 WHERE t0.ZID = ? 
2010-02-05 01:57:51.097 app[393:207] CoreData: annotation: sql connection fetch time: 0.0046s 
2010-02-05 01:57:51.100 app[393:207] CoreData: annotation: total fetch execution time: 0.0074s for 0 rows. 
2010-02-05 01:57:51.125 app[393:207] CoreData: sql: SELECT <a bunch of fields> FROM ZTABLE2 t0 WHERE t0.ZID = ? 
2010-02-05 01:57:51.129 app[393:207] CoreData: annotation: sql connection fetch time: 0.0040s 
2010-02-05 01:57:51.132 app[393:207] CoreData: annotation: total fetch execution time: 0.0071s for 0 rows. 

0.0071s für eine Abfrage auf einem 3GS Gerät ist in Ordnung, aber wenn Sie 100 davon hinzufügen Du hast gerade einen 700ms Blocker bekommen.

In meinem Code, ich bin mit einem Helfer diese Fetches zu tun:

- (MyObject *) myObjectById:(NSNumber *)myObjectId { 
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 
    [fetchRequest setEntity:[self objectEntity]]; // my entity cache  
    [fetchRequest setPredicate:[self objectPredicateById:objectId]]; // predicate cache  
    NSError *error = nil; 
    NSArray *fetchedObjects = [moc executeFetchRequest:fetchRequest error:&error]; 
    if ([fetchedObjects count] == 1) { 
     [fetchRequest release]; 
     return [fetchedObjects objectAtIndex:0]; 
    } 
    [fetchRequest release]; 
    return nil; 
} 

MyObject *obj = [self myObjectById]; 
if (!obj) { 
    // [NSEntityDescription insertNewObjectForEntityForName: ... etc 
} 

Ich finde, dass das falsch ist und ich sollte die Überprüfung auf eine andere Weise tun. Es sollte nur einmal die Datenbank treffen und sollte danach aus dem Speicher kommen, oder? (Das SQL wird auch für Objekte ausgeführt, von denen ich sicher bin, dass sie lokal existieren und mit früheren Abfragen in den Speicher geladen worden sein sollten.) Aber wenn ich myObjectId nur von einer externen Quelle habe, ist dies das Beste, was ich mir vorstellen kann.

Also, die Frage ist vielleicht: Wenn ich myObjectId (eine Core Data Int64-Eigenschaft auf MyObject) habe, wie sollte ich richtig überprüfen, ob das relevante lokale Objekt im CD-Speicher existiert oder nicht? Laden Sie den gesamten Satz möglicher Übereinstimmungen vor und dann ein lokales Array Prädikat?

(Eine mögliche Lösung ist dies zu einem Hintergrund Thread zu verschieben. Das wäre in Ordnung, außer dass, wenn ich die Änderungen aus dem Thread und [Objekte erhalten aus dem Hintergrund thread über Benachrichtigung), blockiert dies immer noch.)

Antwort

9

Lesen Sie "Implementieren Suchen oder Erstellen effizient" in Core Data Programming Guide.

Grundsätzlich müssen Sie ein Array von IDs oder Eigenschaften wie Namen oder alles, was Sie von der verwalteten Objekt Entität haben, erstellen.

Dann müssen Sie ein Prädikat erstellen, das die verwalteten Objekte mit diesem Array filtert.

[fetchRequest setPredicate:[NSPredicate predicateWithFormat: @"(objectID IN %@)", objectIDs]]; 

Natürlich kann "objectIDs" alles sein, was Sie verwenden können, um zu identifizieren. Es muss nicht die NSManagedObjectID sein.

Dann könnten Sie eine Abrufanforderung ausführen und die resultierenden abgerufenen Objekte iterieren, um Duplikate zu finden. Fügen Sie ein neues hinzu, wenn es nicht existiert.

+0

Link? Ich habe den Core Data Programming Guide unter http://developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html gelesen, aber ich erinnere mich nicht daran, dass Find-or-Create da ist . – Jaanus

+6

Hier ist der Link http://developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html#//apple_ref/doc/uid/TP40003174 –

+1

Ich markierte dieses als die Antwort, weil Es enthält die gleiche Idee wie einige andere (cache-relevante Bezeichner-Eigenschaft), hat aber den zusätzlichen Vorteil der Verknüpfung mit der offiziellen Dokumentation. Find-or-Create ist genau das, worum es geht./Da meine Daten klein sind, habe ich nur das gesamte Objekt in den Speicher geladen, da die Daten klein genug sind und ich irgendwann darauf zugreifen muss, und dieser Cache ist superschnell. – Jaanus

3

Sie könnten wahrscheinlich eine Lektion von E-Mail-Clients nehmen.

Sie arbeiten, indem sie zunächst den Server nach einer Liste von Nachrichten-IDs abfragen. Sobald der Client diese Liste hat, wird er mit seinem lokalen Datenspeicher verglichen, um festzustellen, ob etwas anders ist.

Wenn es einen Unterschied gibt, dauert es eine von ein paar Aktionen. 1. Wenn es auf Client, aber nicht Server UND wir sind IMAP, dann lokal löschen. 2. Wenn es auf dem Server, aber nicht auf dem Client vorhanden ist, laden Sie den Rest der Nachricht herunter.

In Ihrem Fall, erste Abfrage für alle IDs. Dann senden Sie eine Follow-up-Abfrage, um alle Daten für diejenigen zu erfassen, die Sie noch nicht haben.

Wenn eine Situation vorliegt, in der der Datensatz möglicherweise lokal vorhanden ist, aber seit dem letzten Download auf dem Server aktualisiert wurde, sollte Ihre Abfrage das Datum der letzten Aktualisierung enthalten.

+0

den Server abfragen, ist für mich billig und asynchron ausgeführt. Mein Problem ist, dass der Teil "Abfrage für alle lokalen IDs" ineffizient ist und/oder ich nicht herausfinden kann, wie es richtig gemacht wird. – Jaanus

1

Es klingt wie, was Sie brauchen, ist ein NSSet von NSManagedObjectIDs, die in den Speicher geladen oder irgendwo schneller Zugriff als Ihr persistenter Objektspeicher gespeichert wird.

Auf diese Weise können Sie Objekt-IDs aus dem Netzwerk mit Objekt-IDs aus Ihrem Cache vergleichen, ohne eine Abrufanforderung für einen großen Datensatz ausführen zu müssen.

Vielleicht fügen Sie die ID aus dem Cache innerhalb von -awakeFromInsert innerhalb Ihrer verwalteten Entity-Klassen?

+0

Objekte aus dem Netzwerk haben eine andere NSNummer "ID", die nicht mit NSManagedObjectID zusammenhängt. Dies ist das Attribut, das ich in dem Beispiel verwende. Also, nur NSSet von NSManagedObjectID-s ist nicht genug, da ich keine "externe ID-> managedId" Zuordnung habe. – Jaanus

3

Sie sollten einen Abruf über alle Objekte durchführen, aber nur die Server-ID für die Objekte abrufen.

Verwenden Sie setPropertiesToFetch: mit setResultType: auf NSDictionaryResultType festgelegt.

1

Nachdem ich mich jahrelang mit dem gleichen Problem herumgeschlagen habe, bin ich über diesen Blogeintrag gestolpert, der ihn vollständig gelöst hat (und ist ein wiederverwendbarer Codeblock als Bonus!).

http://henrik.nyh.se/2007/01/importing-legacy-data-into-core-data-with-the-find-or-create-or-delete-pattern

Während das Codebeispiel des Netzwerkteil nicht abgedeckt ist; Sie müssen es einfach in ein NSDictionary laden. und dann geht es um die Synchronisierung des lokalen Core Data-Kontextes.