pasar - Implementación de importación de datos básicos rápida y eficiente en iOS 5
itunes (1)
Pregunta : ¿Cómo obtengo el contexto de mi hijo para ver los cambios persistidos en el contexto padre para que activen mi NSFetchedResultsController para actualizar la UI?
Aquí está la configuración:
Tienes una aplicación que descarga y agrega muchos datos XML (aproximadamente 2 millones de registros, cada uno más o menos del tamaño de un párrafo normal de texto). El archivo .sqlite tiene un tamaño aproximado de 500 MB. Agregar este contenido a Core Data toma tiempo, pero desea que el usuario pueda usar la aplicación mientras los datos se cargan en el data store de forma incremental. Tiene que ser invisible e imperceptible para el usuario de que se están moviendo grandes cantidades de datos, por lo que no se cuelga ni se agita: se desplaza como la mantequilla. Aún así, la aplicación es más útil, más datos se agregan a ella, por lo que no podemos esperar para siempre 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 solo tiene iOS 5, por lo que el dispositivo más lento que necesita es un iPhone 3GS.
Estos son los recursos que he utilizado hasta ahora para desarrollar mi solución actual:
Guía de programación de datos básicos de Apple: importación eficiente de datos
- Utilice las piscinas de autoalimentación para mantener la memoria hacia abajo
- Costo de las relaciones Importe de forma plana, luego repare las relaciones al final
- No preguntes si puedes evitarlo, ralentiza las cosas de manera O (n ^ 2)
- Importar en lotes: guardar, restablecer, drenar y repetir
- Desactiva el administrador de deshacer al importar
iDeveloper TV - Core Data Performance
- Usar 3 contextos: tipos de contexto de maestro, principal y confinamiento
iDeveloper TV - Actualización de Core Data para Mac, iPhone y iPad
- Ejecutar guarda en otras colas con performBlock hace las cosas rápido.
- La encriptación ralentiza las cosas, apáguela si puedes.
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 bucle de ejecución actual, para que las cosas se sientan bien para el usuario.
- El código de muestra demuestra que es posible hacer grandes importaciones y mantener la UI receptiva, pero no tan rápido como con 3 contextos y guardar de forma sincronizada el 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 rescates puedan ser asincrónicos y, por lo tanto, muy rápidos. Lo creo en el lanzamiento de esta manera:
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext : este es el contexto que la UI usa 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 conecto al contexto maestro allí.
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
Esto realmente 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 10x! Honestamente, esto es difícil de creer. (Este diseño básico debe ser parte de la plantilla estándar de datos básicos ...)
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 / principal que, ostensiblemente, empuja las modificaciones hacia 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 una UITableView que está siendo alimentada con un NSFetchedResultsController. Cuando finaliza el proceso de importación, NSFetchedResultsController no ve cambios desde el contexto principal / principal y, por lo tanto, la IU 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 obtengo el contexto de mi hijo para ver los cambios persistidos en el contexto padre para que activen mi NSFetchedResultsController para actualizar la UI?
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];
}
Probablemente también deberías guardar el maestro MOC en zancadas. No tiene sentido que ese MOC espere hasta el final para guardar. Tiene su propio hilo, y ayudará a mantener la memoria también.
Tu escribiste:
Luego, al final del proceso de importación, guardo en el contexto maestro / principal que, ostensiblemente, empuja las modificaciones hacia los otros contextos secundarios, incluido el contexto principal:
En su configuración, tiene dos hijos (el MOC principal y el MOC de fondo), ambos emparentados con el "maestro".
Cuando guarda en un niño, empuja los cambios hacia el padre. Otros niños de ese MOC verán los datos la próxima vez que realicen una búsqueda ... no se les notificará explícitamente.
Entonces, cuando BG guarda, sus datos son enviados a MASTER. Sin embargo, tenga en cuenta que ninguno de estos datos está en el disco hasta que se haya guardado MASTER. Además, los elementos nuevos no obtendrán ID permanentes hasta que MASTER los guarde en el disco.
En su escenario, está extrayendo los datos en el MOC PRINCIPAL fusionándose desde el guardado MASTER durante la notificación DidSave.
Eso debería funcionar, entonces tengo curiosidad de saber dónde está "colgado". Notaré que no está ejecutando el hilo principal de MOC de forma canónica (al menos no para iOS 5).
Además, probablemente solo esté interesado en fusionar los cambios desde el MOC maestro (aunque su registro parece que es solo así de todos modos). Si tuviera que usar update-on-did-save-notification, haría esto ...
- (void)contextChanged:(NSNotification*)notification {
// Only interested in merging from master into main.
if ([notification object] != masterManagedObjectContext) return;
[mainManagedObjectContext performBlock:^{
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
// NOTE: our MOC should not be updated, but we need to reload the data as well
}];
}
Ahora, por lo que puede ser su verdadero problema con respecto al truco ... muestra dos llamadas diferentes para guardar en el maestro. el primero está bien protegido en su propio performBlock, pero el segundo no (aunque puede estar llamando a saveMasterContext en un performBlock ...
Sin embargo, también cambiaría este código ...
- (void)saveMasterContext {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
// Make sure the master runs in it''s own thread...
[masterManagedObjectContext performBlock:^{
NSError *error = nil;
BOOL saveSuccess = [masterManagedObjectContext save:&error];
// Handle error...
[notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}];
}
Sin embargo, tenga en cuenta que MAIN es un hijo de MASTER. Entonces, no debería tener que fusionar los cambios. En lugar de eso, solo observa el DidSave en el master, ¡y vuelve a intentarlo! Los datos ya están en tus padres, 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 saber acerca de los resultados, son muchos datos) ...
En lugar de hacer que el MOC de fondo sea un hijo del MASTER, conviértalo en un hijo de MAIN.
Toma esto. Cada vez que se guarda BG, automáticamente se empuja hacia la MAIN. Ahora, MAIN tiene que llamar guardar, y luego el maestro tiene que llamar guardar, pero lo único que hacen es mover punteros ... hasta que el maestro los guarde en el disco.
La belleza de ese método es que los datos van desde el MOC de fondo directamente en sus aplicaciones MOC (luego pasa para ser guardado).
Existe una penalización por el paso, pero todo el trabajo pesado se realiza en el MASTER cuando golpea el disco. Y si pateas esas salvaciones en el master con performBlock, entonces el hilo principal simplemente envía la solicitud y vuelve inmediatamente.
¡Por favor déjame saber cómo va!