5

Ho alcune inefficienze nella mia app che mi piacerebbe capire e risolvere.Schema dei dati principali: come aggiornare in modo efficiente le informazioni locali con le modifiche dalla rete?

mio algoritmo è:

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 

sto facendo il controllo sulle linee A e B con la creazione di una query predicato con la chiave del relativo oggetto che fa parte del mio schema. Vedo che sia A (sempre) e B (se l'esecuzione ramificata in quella parte) generano uno SQL SELECT come:

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 per una query va bene su un dispositivo 3G, ma se si aggiungono 100 di questi su, hai appena un blocco da 700ms.

Nel mio codice, sto usando un aiutante di fare queste recupera:

- (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 
} 

sento che questo è sbagliato e avrei dovuto fare il check in qualche altro modo. Dovrebbe solo colpire il database una volta e dovrebbe venire dalla memoria in seguito, giusto? (L'SQL viene eseguito anche per oggetti che so di sicuro esistono localmente e dovrebbero essere stati caricati in memoria con query precedenti.) Ma, se ho solo myObjectId da una fonte esterna, questo è il meglio che potrei pensare.

Quindi, forse la domanda è: se ho myObjectId (una proprietà int64 di Core Data su MyObject), come dovrei controllare correttamente se l'oggetto locale pertinente esiste nell'archivio CD o no? Precaricare l'intera serie di possibili corrispondenze e quindi predicare un array locale?

(Una possibile soluzione è lo spostamento di un thread in background.Questo sarebbe corretto, tranne che quando ottengo le modifiche dal thread e faccio [moc mergeChangesFromContextDidSaveNotification: aNotification]; (ottenere gli oggetti modificati dal thread in background per mezzo di notifica), questo blocca ancora.)

risposta

9

Leggere "Implementazione di Trova-o-Crea in modo efficiente" nella Guida alla programmazione dei dati principali.

Fondamentalmente è necessario creare un array di ID o proprietà come nomi o qualsiasi cosa che si possiede dall'entità dell'oggetto gestito.

Quindi è necessario creare un predicato che filtrerà gli oggetti gestiti utilizzando questo array.

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

Ovviamente "objectIDs" potrebbe essere qualsiasi cosa che è possibile utilizzare per identificare. Non deve essere NSManagedObjectID.

Quindi è possibile eseguire una richiesta di recupero e iterare gli oggetti recuperati risultanti per trovare i duplicati. Aggiungi uno nuovo se non esiste.

+0

Link? Ho letto la Guida alla programmazione dei dati di base su http://developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html, ma non ricordo nulla di Find-or-Create lì . – Jaanus

+6

Ecco il link http://developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html#//apple_ref/doc/uid/TP40003174 –

+1

Ho contrassegnato questo come risposta perché porta la stessa idea di molti altri (proprietà identificatore della cache rilevante), ma ha il vantaggio aggiuntivo di collegarsi alla documentazione ufficiale. Find-or-Create è esattamente ciò di cui si tratta./Per quanto mi riguarda, dato che i miei dati sono piccoli, ho finito con il recupero dell'intero oggetto impostato in memoria, dal momento che i dati sono abbastanza piccoli e ho bisogno di accedervi, e questa modalità cache è super veloce. – Jaanus

3

Probabilmente potresti prendere una lezione dai client di posta elettronica.

Operano innanzitutto interrogando il server per un elenco di ID messaggio. Una volta che il client ha quella lista, allora si confronta con il suo data store locale per vedere se qualcosa è diverso.

Se c'è una differenza, ci vuole una di un paio di azioni. 1. Se esiste sul client, ma non sul server E noi siamo IMAP, quindi eliminare localmente. 2. Se esiste sul server, ma non sul client, quindi scarica il resto del messaggio.

Nel tuo caso, prima query per tutti gli ID. Quindi invia una query di follow-up per prendere tutti i dati per quelli che non hai già.

Se si dispone di una situazione in cui il record potrebbe esistere localmente, ma potrebbe essere stato aggiornato dall'ultimo download sul server, la query deve includere l'ultima data di aggiornamento.

+0

Interrogazione del server è a buon mercato per me e fatto in modo asincrono. Il mio problema è che la parte "query per tutti gli ID locali" è inefficiente e/o non riesco a capire come farlo correttamente. – Jaanus

1

Sembra che quello di cui hai bisogno sia un NSSet di NSManagedObjectIDs che viene caricato in memoria o archiviato da qualche parte più rapidamente accessibile rispetto all'archivio oggetti permanente.

In questo modo è possibile confrontare gli ID oggetto dalla rete con gli ID oggetto dalla cache senza dover eseguire una richiesta di recupero su un set di dati di grandi dimensioni.

Forse aggiungere l'ID alla cache dall'interno di -awakeFromInsert all'interno delle classi di entità gestite?

+0

Gli oggetti della rete hanno un "ID" NSNumber diverso che non è correlato a NSManagedObjectID. Questo è l'attributo che sto usando nell'esempio. Quindi, solo NSSet di NSManagedObjectID-s non è sufficiente, dal momento che non ho alcun mapping "ID esterno> managedId". – Jaanus

3

Si dovrebbe eseguire un recupero su tutti gli oggetti, ma solo recuperare l'ID del server per gli oggetti.

Utilizzare setPropertiesToFetch: con setResultType: impostato su NSDictionaryResultType.

1

Dopo aver litigato con lo stesso problema per anni, ho finalmente imbattuto in questo post di blog che lo ha completamente risolto (ed è un blocco di codice riutilizzabile come bonus!).

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

Mentre il codice di esempio non copre la parte di rete; devi semplicemente caricarlo in un NSDictionary. e quindi questo riguarda la sincronizzazione del contesto locale dei dati di base.

Problemi correlati