Pregunta : ¿Cómo consigo que el contexto de mi hijo vea los cambios persistentes en el contexto principal para que activen mi NSFetchedResultsController para actualizar la interfaz de usuario?
Aquí está la configuración:
Tiene una aplicación que descarga y agrega una gran cantidad de datos XML (aproximadamente 2 millones de registros, cada uno aproximadamente del tamaño de un párrafo de texto normal). El archivo .sqlite tiene un tamaño de aproximadamente 500 MB. Agregar este contenido a Core Data lleva tiempo, pero desea que el usuario pueda usar la aplicación mientras los datos se cargan en el almacén de datos de forma incremental. Tiene que ser invisible e imperceptible para el usuario que se muevan grandes cantidades de datos, por lo que no se cuelga, no hay nerviosismo: se desplaza como mantequilla. Aún así, la aplicación es más útil, cuantos más datos se le agregan, por lo que no podemos esperar una eternidad para que los datos se agreguen al almacén de datos centrales. En el código, esto significa que realmente me gustaría evitar un código como este en el código de importación:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
La aplicación es solo para iOS 5, por lo que el dispositivo más lento que debe admitir es un iPhone 3GS.
Estos son los recursos que he usado hasta ahora para desarrollar mi solución actual:
Guía de programación de datos básicos de Apple: Importación de datos de forma eficiente
- Utilice grupos de liberación automática para mantener baja la memoria
- Costo de relaciones. Importe planos, luego repare las relaciones al final
- No preguntes si puedes evitarlo, ralentiza las cosas de una manera O (n ^ 2)
- Importar en lotes: guardar, restablecer, vaciar y repetir
- Desactive el Administrador de deshacer al importar
iDeveloper TV: rendimiento de datos básicos
- Utilice 3 contextos: tipos de contexto maestro, principal y confinamiento
iDeveloper TV - Actualización de Core Data para Mac, iPhone y iPad
- La ejecución guarda en otras colas con performBlock agiliza las cosas.
- El cifrado ralentiza las cosas, apáguelo si puede.
Importación y visualización de grandes conjuntos de datos en datos básicos por Marcus Zarra
- Puede ralentizar la importación dando tiempo al ciclo de ejecución actual, de modo que las cosas se sientan bien para el usuario.
- El código de muestra demuestra que es posible realizar grandes importaciones y mantener la interfaz de usuario receptiva, pero no tan rápido como con 3 contextos y el guardado asincrónico en disco.
Mi solución actual
Tengo 3 instancias de NSManagedObjectContext:
masterManagedObjectContext : este es el contexto que tiene NSPersistentStoreCoordinator y es responsable de guardar en el disco. Hago esto para que mis guardados puedan ser asincrónicos y, por lo tanto, muy rápidos. Lo creo en el lanzamiento así:
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext : este es el contexto que usa la interfaz de usuario en todas partes. Es un elemento secundario del masterManagedObjectContext. Lo creo así:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
backgroundContext : este contexto se crea en mi subclase NSOperation que es responsable de importar los datos XML en Core Data. Lo creo en el método principal de la operación y lo vinculo al contexto maestro allí.
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
En realidad, esto funciona muy, MUY rápido. ¡Con solo hacer esta configuración de 3 contextos, pude mejorar mi velocidad de importación en más de 10 veces! Honestamente, esto es difícil de creer. (Este diseño básico debe ser parte de la plantilla de datos básicos estándar ...)
Durante el proceso de importación, guardo 2 formas diferentes. Cada 1000 elementos que guardo en el contexto de fondo:
BOOL saveSuccess = [backgroundContext save:&error];
Luego, al final del proceso de importación, guardo en el contexto maestro / padre que, aparentemente, empuja las modificaciones a los otros contextos secundarios, incluido el contexto principal:
[masterManagedObjectContext performBlock:^{
NSError *parentContextError = nil;
BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
Problema : el problema es que mi interfaz de usuario no se actualizará hasta que vuelva a cargar la vista.
Tengo un UIViewController simple con un UITableView que se alimenta de datos mediante un NSFetchedResultsController. Cuando se completa el proceso de importación, NSFetchedResultsController no ve cambios del contexto principal / maestro y, por lo tanto, la interfaz de usuario no se actualiza automáticamente como estoy acostumbrado a ver. Si saco el UIViewController de la pila y lo vuelvo a cargar, todos los datos están allí.
Pregunta : ¿Cómo consigo que el contexto de mi hijo vea los cambios persistentes en el contexto principal para que activen mi NSFetchedResultsController para actualizar la interfaz de usuario?
He intentado lo siguiente que simplemente cuelga la aplicación:
- (void)saveMasterContext {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
NSError *error = nil;
BOOL saveSuccess = [masterManagedObjectContext save:&error];
[notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == mainManagedObjectContext) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:YES];
return;
}
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
Respuestas:
Probablemente también debería guardar el MOC maestro a pasos agigantados. No tiene sentido que ese MOC espere hasta el final para ahorrar. Tiene su propio hilo y también ayudará a reducir la memoria.
Tu escribiste:
En su configuración, tiene dos hijos (el MOC principal y el MOC de fondo), ambos vinculados al "maestro".
Cuando ahorras en un hijo, los cambios se trasladan al padre. Otros hijos de ese MOC verán los datos la próxima vez que realicen una búsqueda ... no se les notifica explícitamente.
Entonces, cuando BG guarda, sus datos se envían a MASTER. Sin embargo, tenga en cuenta que ninguno de estos datos está en el disco hasta que MASTER los guarda. Además, los elementos nuevos no obtendrán ID permanentes hasta que MASTER los guarde en el disco.
En su escenario, está introduciendo los datos en el MOC PRINCIPAL fusionando desde el guardado MAESTRO durante la notificación DidSave.
Eso debería funcionar, así que tengo curiosidad por saber dónde está "colgado". Notaré que no se está ejecutando en el hilo principal de MOC de la forma canónica (al menos no para iOS 5).
Además, probablemente solo esté interesado en fusionar los cambios del MOC maestro (aunque su registro parece que es solo para eso de todos modos). Si tuviera que usar la notificación de actualización al guardar, haría esto ...
Ahora, para lo que puede ser su problema real con respecto al bloqueo ... muestra dos llamadas diferentes para guardar en el maestro. el primero está bien protegido en su propio performBlock, pero el segundo no (aunque es posible que esté llamando a saveMasterContext en un performBlock ...
Sin embargo, también cambiaría este código ...
Sin embargo, tenga en cuenta que MAIN es un hijo de MASTER. Por lo tanto, no debería tener que fusionar los cambios. En su lugar, solo busque DidSave en el maestro y vuelva a buscar. Los datos ya están en tu padre, esperando que los solicites. Ese es uno de los beneficios de tener los datos en el padre en primer lugar.
Otra alternativa a considerar (y me interesaría conocer sus resultados, son muchos datos) ...
En lugar de hacer que el MOC de fondo sea un hijo del MASTER, conviértalo en un hijo del MAIN.
Toma esto. Cada vez que el BG se guarda, se empuja automáticamente al PRINCIPAL. Ahora, el MAIN tiene que llamar a save, y luego el maestro tiene que llamar a save, pero lo único que hacen es mover punteros ... hasta que el maestro guarda en el disco.
La belleza de ese método es que los datos van desde el MOC de fondo directamente al MOC de sus aplicaciones (luego pasan a través para guardarse).
Hay alguna penalización por el traspaso, pero todo el trabajo pesado se hace en el MASTER cuando golpea el disco. Y si patea esos guardados en el maestro con performBlock, entonces el hilo principal simplemente envía la solicitud y regresa inmediatamente.
¡Por favor déjame saber cómo va!
fuente