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];
}