2012-07-04 21 views
7

Estoy construyendo mi primera aplicación para iOS, que en teoría debería ser bastante sencilla, pero tengo dificultades para hacerlo suficientemente a prueba de balas para que me sienta seguro de enviarla a la tienda de aplicaciones.Uso de datos centrales simultánea y confiablemente

En pocas palabras, la pantalla principal tiene una vista de tabla, al seleccionar una fila, pasa a otra vista de tabla que muestra la información relevante para la fila seleccionada en una forma de detalle maestro. Los datos subyacentes se recuperan como datos JSON de un servicio web una vez al día y luego se guardan en caché en un almacén de datos centrales. Los datos anteriores a ese día se eliminan para evitar que el archivo de base de datos SQLite crezca indefinidamente. Todas las operaciones de persistencia de datos se realizan utilizando Datos centrales, con un NSFetchedResultsController que sustenta la vista de tabla de detalles.

El problema que estoy viendo es que si cambia rápidamente entre las pantallas maestra y de detalle varias veces mientras se recuperan datos nuevos, se analizan y se guardan, la aplicación se congela o se cuelga por completo. Parece que hay algún tipo de condición de carrera, tal vez debido a que Core Data importa datos en segundo plano mientras el hilo principal intenta realizar una búsqueda, pero estoy especulando. He tenido problemas para capturar cualquier información de bloqueo significativa, por lo general es un SIGSEGV en la pila de Core Data.

La siguiente tabla muestra el orden real de eventos que ocurren cuando se carga el detalle controlador de vista tabla:

 
Main Thread       Background Thread 
viewDidLoad 

            Get JSON data (using AFNetworking) 

Create child NSManagedObjectContext (MOC) 

            Parse JSON data 
            Insert managed objects in child MOC 
            Save child MOC 
            Post import completion notification 

Receive import completion notification 
Save parent MOC 
Perform fetch and reload table view 

            Delete old managed objects in child MOC 
            Save child MOC 
            Post deletion completion notification 

Receive deletion completion notification 
Save parent MOC 

Una vez que el bloque de terminación AFNetworking se activa cuando ha llegado los datos JSON, se crea un anidado NSManagedObjectContext y se pasa a un objeto "importador" que analiza los datos JSON y guarda los objetos en el almacén de datos centrales. El importador realiza utilizando el nuevo performBlock método introducido en iOS 5:

NSManagedObjectContext *child = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
    [child setParentContext:self.managedObjectContext];   
    [child performBlock:^{ 
     // Create importer instance, passing it the child MOC... 
    }]; 

El objeto importador observa de su propio MOC NSManagedObjectContextDidSaveNotification y luego registra su propia notificación que se observa por el controlador detalle vista de tabla. Cuando se publica esta notificación, el controlador de vista de tabla realiza un guardado en su propio MOC (principal).

Uso el mismo patrón básico con un objeto "eliminador" para eliminar los datos antiguos después de que se hayan importado los datos nuevos del día. Esto ocurre de forma asincrónica una vez que el controlador de resultados recuperado ha obtenido los nuevos datos y se ha vuelto a cargar la vista de tabla de detalles.

Una cosa que no estoy haciendo es observar cualquier combinación de notificaciones o bloquear ninguno de los contextos de objetos administrados o el coordinador de tienda persistente. ¿Esto es algo que debería estar haciendo? No estoy seguro de cómo diseñar esto correctamente, así que agradecería cualquier consejo.

Respuesta

2

Sólo una idea arquitectónica:

Con su patrón de actualización de datos establecida (una vez al día, ciclo completo de datos borrados y añadido), que en realidad estemos motivados para crear un nuevo almacén persistente cada día (es decir, el nombre de la fecha del calendario), y luego en la notificación de finalización, haga que la vista de tabla configure un nuevo controlador de resultados recuperados asociado con el nuevo almacén (y probablemente un nuevo MOC), y actualice usando eso. Entonces la aplicación puede (en cualquier otro lugar, tal vez también desencadenada por esa notificación) destruir por completo el "viejo" almacén de datos. Esta técnica desacopla el procesamiento de actualización del almacén de datos que la aplicación está utilizando actualmente, y el "cambio" a los nuevos datos puede considerarse mucho más atomic, ya que el cambio simplemente comienza a apuntar a los nuevos datos en lugar de esperando que no esté atrapando la tienda en un estado inconsistente mientras se escriben nuevos datos (pero aún no está completo).

Obviamente he dejado algunos detalles, pero tiendo a pensar que muchos datos que se cambian mientras se usa deben modificarse para reducir la probabilidad del tipo de bloqueo que está experimentando.

feliz para discutir más ...

+0

Me gusta esta idea, me gusta ** mucho **. También tiene la ventaja de que eliminar un sistema de archivos del archivo SQLite será mucho más rápido que tener los datos centrales eliminar los objetos administrados en el gráfico del objeto, aunque, por supuesto, en realidad el rendimiento de eliminación no importa porque una tienda persistente diferente será usado de todos modos. Voy a dar un enfoque a este fin de semana. –

3

Pre-IOS 5, hemos por lo general tenían dos NSManagedObjectContexts: uno para el hilo principal, uno para un subproceso en segundo plano. El hilo de fondo puede cargar o borrar datos y luego guardar. El resultante NSManagedObjectContextDidSaveNotification fue pasado (como lo está haciendo) al hilo principal. Llamamos al mergeChangesFromManagedObjectContextDidSaveNotification: para ponerlos en el contexto del hilo principal. Esto ha funcionado bien para nosotros.

Un aspecto importante de esto es que el save: en el subproceso de fondo bloques hasta después de las mergeChangesFromManagedObjectContextDidSaveNotification: acabados que se ejecuta en el hilo principal (debido a que llamamos mergeChanges ... por parte del oyente a la notificación). Esto garantiza que el contexto del objeto gestionado de subproceso principal vea esos cambios. No sé si necesita para hacer esto si tiene una relación padre-hijo, pero lo hizo en el modelo anterior para evitar varios tipos de problemas.

no estoy seguro de cuál es la ventaja de tener una relación de padre-hijo entre los dos contextos es. Parece de su descripción que el último guardado en el disco ocurre en el hilo principal, que probablemente no sea ideal por razones de rendimiento. (Especialmente si está borrando una gran cantidad de datos, el mayor costo de eliminación en nuestras aplicaciones siempre ha ocurrido durante el último guardado en el disco.)

¿Qué código está ejecutando cuando los controladores aparecen/desaparecen que podrían estar causando problemas de datos centrales? ¿En qué tipo de rastros de pila está viendo el bloqueo?

+0

Gracias. En realidad, no estoy usando 'mergeChangesFromManagedObjectContextDidSaveNotification:' porque en el uso normal los nuevos datos se muestran perfectamente bien sin él. Sin embargo, lo he usado en una compilación anterior y tuve las mismas fallas cuando cambio rápidamente entre las dos pantallas. –

2

El principal problema que he tenido con los datos multi-hilo de núcleo está accediendo inadvertidamente un objeto administrado en un hilo/queue distinto del que fue creado en.

He encontrado una buena herramienta de depuración es agregue los insertos NSA para verificar que los objetos administrados creados en su contexto principal de objetos administrados solo se utilicen allí, y los creados en un contexto de fondo no se usen en el contexto principal.

Esto implicará la subclasificación NSManagedObjectContext y NSManagedObject:

  • Añadir una Ivar a la subclase MOC y asignarle la cola que se creó el.
  • Su subclase MO debe comprobar la cola actual es el mismo que propiedad de cola de su MOC.

Son solo unas pocas líneas de código, pero a largo plazo pueden evitar que cometas errores difíciles de rastrear.

+0

Gracias, pero no estoy seguro de cómo obtener la cola de MOC ya que está siendo instanciado usando 'initWithConcurrencyType: NSPrivateQueueConcurrencyType', lo que significa que crea su propia cola de despacho privada. –

+0

¿Qué hay de dispatch_get_current_queue dentro de su performBlock: –

+0

Esa cola es privada; intentar acceder directamente es algo que la gente de Apple califica como una mala idea. –

2

NSFetchedResultsController ha demostrado ser un poco sensible a las eliminaciones masivas de modo que es donde me gustaría empezar a cavar primero.

Mi pregunta inicial es, ¿cómo son las vuelva a recuperar y recarga de tableview relacionado con el inicio de la operación de eliminación. ¿Existe la posibilidad de que el bloque de eliminación guarde el MOC hijo mientras el NSFetchedResultsController sigue llegando o no?

¿Es posible que cuando se cambia de vista de detalle de dominar y luego de nuevo a vista de detalle habrá múltiples tareas en segundo plano concurrentes que se ejecutan?¿O está recuperando todos los datos del servicio web de una vez, no solo los relevantes para una fila en particular?

Una alternativa para hacerlo más robusto es usar un patrón similar al que utiliza UIManagedDocument:

En lugar de utilizar un MOC de los padres como principal Modelo de rosca de concurrencia, UIManagedDocument crea realmente el principal MOC como cola privada y hace que el MOC infantil disponible para su uso en el hilo principal. El beneficio aquí es que todas las E/S permanecen en segundo plano y se guardan en el MOC padre. Esto no interfiere con el MOC del niño hasta que el MOC del niño se hace saber explícitamente sobre él. Eso se debe a que save comete cambios de niño a padre y no al revés.

Por lo tanto, si realizó sus eliminaciones en una cola principal que es privada, eso no terminaría en el alcance NSFetchedResultsController. Y dado que se trata de datos antiguos, esa es en realidad la forma preferida.

Una alternativa Ofrezco es utilizar tres contextos:

principal MOC (NSPrivateQueueConcurrencyType)

  • Responsable de almacén persistente y cancelación de sus datos antiguos.

Niño MOC Un (NSMainQueueConcurrencyType)

  • responsable de todo lo relacionado con la interfaz de usuario y NSFetchedResultsController

Niño MOC B (NSPrivateQueueConcurrencyType, hijo del niño MOC A)

  • Responsable de insertar nuevos datos y confirmarlos hasta el MOC infantil A cuando haya terminado.
Cuestiones relacionadas