5

Tengo un poco de ineficiencia en mi aplicación que me gustaría entender y corregir.Patrón de datos básicos: ¿cómo actualizar eficientemente la información local con los cambios de la red?

Mi algoritmo es:

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 

que estoy haciendo la comprobación de las líneas A y B mediante la creación de una consulta predicado con la clave del objeto relevante que es parte de mi esquema. Veo que tanto A (siempre) y B (si la ejecución bifurca en esa parte) generan un SQL seleccionar como:

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 para una consulta está bien en un dispositivo 3G, pero si se agrega 100 de éstos arriba, acabas de obtener un bloqueador de 700ms.

En mi código, estoy usando un ayudante para hacer estas recuperaciones:

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

siento que esto está mal y que debería hacer la comprobación de alguna otra manera. Solo debería golpear la base de datos una vez y debería venir de la memoria a partir de entonces, ¿verdad? (El SQL se ejecuta incluso para objetos que sé que existen localmente y deberían haber sido cargados en la memoria con consultas previas). Pero si solo tengo myObjectId de una fuente externa, esto es lo mejor que se me ocurre.

Entonces, quizás la pregunta es: si tengo myObjectId (una propiedad int64 de Core Data en MyObject), ¿cómo debería verificar correctamente si el objeto local relevante existe en la tienda de CD o no? Precargar todo el conjunto de posibles coincidencias y luego predicar una matriz local?

(Una posible solución es mover esto a un hilo de fondo. Esto estaría bien, excepto que cuando obtengo los cambios del hilo y hago [moc mergeChangesFromContextDidSaveNotification: aNotification]; (obteniendo objetos cambiados del hilo del fondo por medio de notificación), esto todavía bloquea.)

Respuesta

9

Lea "Implementación de Buscar-o-Crear de manera eficiente" en la Guía de programación de datos básicos.

Básicamente, debe crear una matriz de ID o propiedades como nombres, o cualquier cosa que tenga de la entidad del objeto gestionado.

Luego necesita crear un predicado que filtre los objetos administrados usando esta matriz.

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

Por supuesto "ObjectID" podría ser cualquier cosa que se puede utilizar para identificar. No tiene que ser el NSManagedObjectID.

Luego, puede hacer una solicitud de búsqueda e iterar los objetos recuperados resultantes para buscar duplicados. Agregue uno nuevo si no existe.

+0

Enlace? He leído la Guía de programación de datos básicos en http://developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html, pero no recuerdo nada de que Find-or-Create esté allí. . – Jaanus

+6

Aquí está el enlace http://developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html#//apple_ref/doc/uid/TP40003174 –

+1

Marqué esta como la respuesta porque lleva la misma idea que varios otros (propiedad de identificador relevante de caché), pero tiene la ventaja adicional de vincular a la documentación oficial. Find-or-Create es exactamente de lo que se trata./En cuanto a mí, dado que mis datos son pequeños, terminé simplemente buscando todo el conjunto de objetos en la memoria, ya que los datos son lo suficientemente pequeños y necesito acceder finalmente, y esta forma de almacenamiento en caché es superrápida. – Jaanus

3

Probablemente pueda aprender algo de los clientes de correo electrónico.

Funcionan al consultar primero al servidor para obtener una lista de ID de mensajes. Una vez que el cliente tiene esa lista, luego se compara con su tienda de datos local para ver si algo es diferente.

Si hay una diferencia, se necesita una de dos acciones. 1. Si existe en el cliente, pero no en el servidor Y somos IMAP, entonces borramos localmente. 2. Si existe en el servidor, pero no en el cliente, descargue el resto del mensaje.

En su caso, primera consulta para todos los ID. A continuación, envíe una consulta de seguimiento para obtener todos los datos de los que aún no tiene.

Si tiene una situación donde el registro puede existir localmente, pero podría haber sido actualizado desde la última descarga en el servidor, su consulta debería incluir la última fecha actualizada.

+0

Consulta del servidor es barato para mí y hecho de forma asíncrona. Mi problema es que la parte "consulta para todos los ID locales" es ineficaz y/o no puedo entender cómo hacerlo correctamente. – Jaanus

1

Parece que lo que necesita es un NSSet de NSManagedObjectIDs que se carga en la memoria o se almacena en un lugar al que se accede más rápidamente que su almacén de objetos persistentes.

De esta forma, puede comparar los identificadores de objetos de la red con identificadores de objetos de su caché sin tener que realizar una solicitud de recuperación en un conjunto de datos grande.

¿Tal vez agregar la ID a la memoria caché desde dentro de -awakeFromInsert dentro de las clases de entidad gestionada?

+0

Los objetos de la red tienen un "ID" NSNumber diferente que no está relacionado con NSManagedObjectID. Este es el atributo que estoy usando en el ejemplo. Por lo tanto, solo NSSet de NSManagedObjectID-s no es suficiente, ya que no tengo una asignación de "ID externo -> managedID". – Jaanus

1

Después de luchar con el mismo problema por siglos, finalmente me topé con esta entrada de blog que la resolvió totalmente (¡y es un bloque de código reutilizable como una bonificación!).

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

Si bien el ejemplo de código no cubre la parte de red; simplemente necesita cargarlo en un NSDictionary. y luego se trata de la sincronización del contexto de datos básicos locales.

Cuestiones relacionadas