Guardar NSManagedObjectContext sin tocar el hilo principal

Lo que trato de hacer:

  • Realice la synchronization de background con una API web sin congelar la interfaz de usuario. Estoy usando MagicalRecord pero no es realmente específico.
  • Asegúrate de estar usando contexts y tan correctamente.

Lo que realmente es mi pregunta: ¿mi entendimiento es correcto? Además de un par de preguntas al final.

Entonces, los contexts puestos a disposition por MagicalRecord son:

  • MR_rootSavingContext de PrivateQueueConcurrencyType que se usa para mantener los datos en la tienda, que es un process lento
  • MR_defaultContext of MainQueueConcurrencyType
  • y para el background querría trabajar con un context generado por MR_context () , que es hijo de MR_defaultContext y es de PrivateQueueConcurrencyType

Ahora, para save de manera asíncrona, tenemos dos opciones:

  • MR_saveToPersistentStoreWithCompletion () que saveá todo el path hasta MR_rootSavingContext y escribirá en el disco
  • MR_saveOnlySelfWithCompletion () que saveá solo hasta el context principal (i? E. MR_defaultContext para un context creado con MR_context)

A partir de ahí, pensé que podría hacer lo siguiente (llamémoslo Intento # 1) sin congelar la interfaz de usuario:

let context = NSManagedObjectContext.MR_context() for i in 1...1_000 { let user = User.MR_createInContext(context) as User context.MR_saveOnlySelfWithCompletion(nil) } // I would normally call MR_saveOnlySelfWithCompletion here, but calling it inside the loop makes any UI block easier to spot 

Pero, mi suposition era incorrecta. Miré en MR_saveOnlySelfWithCompletion y vi que se basa en

 [self performBlock:saveBlock]; 

que según Apple Docs

Asincrónicamente realiza un bloque determinado en la queue del receptor.

Así que estaba un poco desconcertado, ya que esperaba que no bloqueara la interfaz de usuario debido a eso.

Luego lo intenté (llamémosle Intento # 2)

 let context = NSManagedObjectContext.MR_context() for i in 1...1_000 { let user = User.MR_createInContext(context) as User dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in context.MR_saveOnlySelfWithCompletion(nil) } } 

Y esto hace el trabajo, pero no se siente bien.

Luego encontré algo en las notas de la versión de iOS 5.0

Al enviar posts a un context creado con una asociación de queue, debe utilizar el método performBlock: o performBlockAndWait: si su código no se está ejecutando en esa queue (para el tipo de queue principal) o dentro del scope de una invocación de performBlock … (para el tipo de queue privada). Dentro de los bloques pasados ​​a esos methods, puede usar los methods de NSManagedObjectContext libremente.

Entonces, estoy asumiendo eso:

  • Intento # 1 congela la interfaz de usuario porque en realidad la llamo desde la queue principal y no dentro del scope de un bloque de ejecución
  • Intento # 2 funciona, pero estoy creando otro hilo mientras que el context ya tiene su propio hilo de background

Entonces, por supuesto, lo que debería hacer es usar saveWithBlock:

 MagicalRecord.saveWithBlock { (localContext) -> Void in for i in 1...1_000 { User.MR_createInContext(context) } } 

Esto realiza la operación en un hijo directo de MR_rootSavingContext que es de PrivateQueueConcurrencyType. Gracias a rootContextChanged , cualquier cambio que vaya hasta MR_rootSavingContext estará disponible para MR_defaultContext.

Entonces parece que:

  • MR_defaultContext es el context perfecto cuando se trata de mostrar datos
  • las ediciones se realizan preferiblemente en un MR_context (hijo de MR_defaultContext)
  • las tareas de larga ejecución, como una synchronization de server, se realizan preferentemente con saveWithBlock

Lo que aún no se obtiene es cómo trabajar con MR_save […] WithCompletion (). Lo usaría en MR_context, pero como bloquea el hilo principal en mis casos de testing, no veo cuándo se vuelve relevante (o lo que me perdí …).

Gracias por tu time 🙂

Ok, rara vez estoy usando loggings mágicos, pero como dijiste que la pregunta es más general intentaré una respuesta.

Alguna teoría: al crear un context, se pasa un indicador en cuanto a si quiere que se encuadre en el hilo principal o en el background

 let context = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType) 

Por "atado" queremos decir que un hilo es referencedo internamente por el context. En el ejemplo anterior se crea un nuevo hilo y es propiedad del context. Este hilo no se utiliza automáticamente, pero debe llamarse explícitamente como:

 context.performBlock({ () -> Void in context.save(nil) return }); 

Por lo tanto, su código con 'dispatch_async' es incorrecto porque el hilo al que está vinculado el context solo se puede hacer reference desde el context mismo (es un hilo privado).

Lo que debe inferir de lo anterior es que si el context está vinculado al hilo principal, llamar a performBlock desde el hilo principal no hará nada diferente a los methods de context de llamada directamente.

Para comentar sus puntos de bala al final:

  • MR_defaultContext es el context perfecto cuando se trata de mostrar datos : Se debe acceder a un NSManagedObject desde el context en que se creó, de modo que en realidad es el único context desde el que se puede alimentar la interfaz de usuario.

  • las ediciones se realizan preferiblemente en un MR_context (hijo de MR_defaultContext) : las ediciones no son caras y debe seguir la regla anterior. Si llama a una function que edita las properties de un NSManagedObject desde el hilo principal (como al tocar un button), debe actualizar el context principal. Los salvos por otro lado son caros y es por eso que su context principal no debe vincularse directamente a una tienda persistente sino que simplemente empuja sus ediciones a un context raíz con concurrency de background que posee un almacén persistente.

  • las tareas de larga ejecución como una synchronization de server se realizan preferentemente con saveWithBlock Yes.

Ahora, en el bash 1

 for i in 1...1_000 { let user = User.MR_createInContext(context) as User } context.MR_saveOnlySelfWithCompletion(nil) 

No hay necesidad de save para cada creación de objects. Incluso si la interfaz de usuario no estaba bloqueada, es un desperdicio.

Acerca de MR_context. En la documentation para loggings mágicos no puedo ver un 'MR_context', así que me pregunto si es un método rápido para acceder al context principal. Si es así, se bloqueará.

Lea este artículo, lo encontré útil. http://martiancraft.com/blog/2015/03/core-data-stack/ ?