ios core-data asynchronous synchronization nsmanagedobjectcontext

ios - Diferencias de comportamiento entre performBlock: y performBlockAndWait:?



core-data asynchronous (1)

Estoy creando un NSManagedObjectContext en una cola privada para manejar las actualizaciones de datos que tomo de los archivos y / o servicios:

NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]; privateContext.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator;

Ya que estoy usando una cola privada, no entiendo completamente la diferencia entre los performBlock: y performBlockAndWait: ... Para realizar mis actualizaciones de datos, actualmente estoy haciendo esto:

[privateContext performBlock: ^{ // Parse files and/or call services and parse // their responses // Save context [privateContext save:nil]; dispatch_async(dispatch_get_main_queue(), ^{ // Notify update to user }); }];

En este caso, mis actualizaciones de datos se realizan de forma sincronizada y secuencial, por lo que supongo que es el lugar correcto para guardar el contexto, ¿verdad? Si estoy haciendo algo mal, te agradecería que me lo hicieras saber. Por otro lado, ¿sería este código equivalente ?:

[privateContext performBlockAndWait: ^{ // Parse files and/or call services and parse // their responses // Save context [privateContext save:nil]; }]; // Notify update to user

Una vez más, supongo que es el lugar correcto para guardar el contexto ... ¿cuáles son las diferencias entre ambos métodos (si hay alguno, en este caso)?

¿Qué sucede si, en lugar de realizar llamadas de servicio o análisis de archivos síncronos, debo realizar llamadas de servicio asíncronas? ¿Cómo se gestionarán estas actualizaciones de datos?

Gracias por adelantado


Tienes razón en que cualquier cosa que quieras hacer con un MOC debe hacerse dentro de performBlock o performBlockAndWait . Tenga en cuenta que retener / liberar es seguro para subprocesos para objetos administrados, por lo que no tiene que estar dentro de uno de esos bloques para retener / liberar recuentos de referencia en objetos administrados.

Ambos utilizan una cola síncrona para procesar mensajes, lo que significa que solo se ejecutará un bloque a la vez. Bueno, eso es casi cierto. Ver las descripciones de performBlockAndWait . En cualquier caso, el acceso al MOC se serializará de modo que solo un hilo acceda al MOC a la vez.

tl; dr No te preocupes por la diferencia, y siempre usa performBlock .

Diferencias de hecho

Hay una serie de diferencias. Estoy seguro de que hay más, pero aquí están las que creo que son más importantes de entender.

Síncrono vs Asíncrono

performBlock es asíncrono, ya que regresa de inmediato, y el bloque se ejecuta en algún momento en el futuro, en algún hilo no revelado. Todos los bloques dados al MOC a través de performBlock se ejecutarán en el orden en que se agregaron.

performBlockAndWait es síncrono, ya que el subproceso que llama esperará hasta que el bloque se haya ejecutado antes de regresar. Si el bloque se ejecuta en algún otro subproceso o se ejecuta en el subproceso de llamada no es tan importante y es un detalle de implementación en el que no se puede confiar.

Sin embargo, tenga en cuenta que podría implementarse como "Oye, otro hilo, ve a ejecutar este bloque. Me quedaré aquí sin hacer nada hasta que me digas que está hecho". O, podría implementarse como "Hey, Core Data, dame un bloqueo que evite que todos los otros bloques se ejecuten para que pueda ejecutar este bloque en mi propio hilo". O podría ser implementado de alguna otra manera. De nuevo, detalle de implementación, que podría cambiar en cualquier momento.

Sin embargo, te diré esto, la última vez que lo probé, performBlockAndWait ejecutó el bloque en el subproceso de llamada (es decir, la segunda opción en el párrafo anterior). Esta es solo información para ayudarlo a comprender lo que está sucediendo, y no debe confiarse de ninguna manera.

Reentrancia

performBlock siempre es asíncrono, y por lo tanto no es reentrante. Bueno, algunos pueden considerarlo reentrante, ya que puede llamarlo desde un bloque al que se llamó con performBlock . Sin embargo, si haces esto, todas las llamadas a performBlock volverán inmediatamente, y el bloque no se ejecutará hasta que al menos el bloque que se está ejecutando termine completamente su trabajo.

[moc performBlock:^{ doSomething(); [moc performBlock:^{ doSomethingElse(); }]; doSomeMore(); }];

Estas funciones siempre se ejecutarán en este orden:

doSomething() doSomeMore() doSomethingElse()

performBlockAndWait siempre es síncrono. Además, también es reentrante. Las llamadas múltiples no se bloquearán. Por lo tanto, si terminas llamando a performBlockAndWait mientras estás dentro de un bloque que se estaba ejecutando como resultado de otro performBlockAndWait , entonces está bien. Obtendrá el comportamiento esperado, ya que la segunda llamada (y cualquier llamada subsiguiente) no causará un interbloqueo. Además, el segundo se ejecutará completamente antes de que regrese, como es de esperar.

[moc performBlockAndWait:^{ doSomething(); [moc performBlockAndWait:^{ doSomethingElse(); }]; doSomeMore(); }];

Estas funciones siempre se ejecutarán en este orden:

doSomething() doSomethingElse() doSomeMore()

FIFO

FIFO significa "Primero en entrar, primero en salir", lo que significa que los bloques se ejecutarán en el orden en que se colocaron en la cola interna.

performBlock siempre respeta la estructura FIFO de la cola interna. Cada bloque se insertará en la cola y solo se ejecutará cuando se elimine, en orden FIFO.

Por definición, performBlockAndWait interrumpe el pedido FIFO porque salta la cola de bloques que ya se han puesto en cola.

Los bloques enviados con performBlockAndWait no tienen que esperar otros bloques que se ejecutan en la cola. Hay varias maneras de ver esto. Una simple es esta.

[moc performBlock:^{ doSomething(); [moc performBlock:^{ doSomethingElse(); }]; doSomeMore(); [moc performBlockAndWait:^{ doSomethingAfterDoSomethingElse(); }]; doTheLastThing(); }];

Estas funciones siempre se ejecutarán en este orden:

doSomething() doSomeMore() doSomethingAfterDoSomethingElse() doTheLastThing() doSomethingElse()

Es obvio en este ejemplo, por lo que lo usé. Considere, sin embargo, si su MOC está recibiendo llamadas desde múltiples lugares. Podría ser un poco confuso.

El punto a recordar, sin embargo, es que performBlockAndWait es preventivo y puede saltar la cola FIFO.

Punto muerto

Nunca obtendrás un interbloqueo llamando a performBlock . Si haces algo estúpido dentro del bloque, entonces podrías bloquear, pero llamar a performBlock nunca lo hará. Puede llamarlo desde cualquier lugar, y simplemente agregará el bloque a la cola y lo ejecutará en algún momento en el futuro.

Puede obtener puntos muertos fácilmente llamando a performBlockAndWait , especialmente si lo llama desde un método al que una entidad externa puede llamar o indiscriminadamente dentro de contextos anidados. Específicamente, casi se garantiza que performBlockAndWait aplicaciones si un padre llama a performBlockAndWait en un hijo.

Eventos del usuario

Core Data considera que un "evento de usuario" es cualquier cosa entre llamadas a processPendingChanges . Puede leer los detalles de por qué este método es importante, pero lo que sucede en un "evento de usuario" tiene implicaciones en las notificaciones, la administración de deshacer, la propagación de eliminación, la fusión de cambios, etc.

performBlock encapsula un "evento de usuario" lo que significa que el bloque de código se ejecuta automáticamente entre llamadas distintas a processPendingChanges .

performBlockAndWait no encapsula un "evento de usuario". Si desea que el bloqueo se trate como un evento de usuario distinto, debe hacerlo usted mismo.

Auto Release Pool

performBlock envuelve el bloque en su propio autoreleasepool.

performBlockAdWait no proporciona un único conjunto de autorelease. Si necesitas uno, debes proporcionarlo tú mismo.

Opiniones personales

Personalmente, no creo que haya muchas buenas razones para usar performBlockAndWait . Estoy seguro de que alguien tiene un caso de uso que no se puede lograr de otra manera, pero aún no lo he visto. Si alguien sabe de ese caso de uso, por favor compártelo conmigo.

Lo más cercano es llamar a performBlockAndWait en un contexto principal (nunca hagas esto en un MOC de tipo de NSMainConcurrencyType porque podría bloquear tu interfaz de usuario). Por ejemplo, si desea asegurarse de que la base de datos se haya guardado completamente en el disco antes de que el bloque actual vuelva y otros bloques tengan la oportunidad de ejecutarse.

Así, hace un tiempo, decidí tratar los Datos Core como una API completamente asíncrona. Como resultado, tengo una gran cantidad de código de datos básicos, y no tengo una sola llamada para realizar performBlockAndWait y performBlockAndWait , fuera de las pruebas.

La vida es mucho mejor así. Tengo muchos menos problemas que cuando pensé "debe ser útil o no lo habrían proporcionado".

Ahora, simplemente ya no tengo ninguna necesidad de performBlockAndWait . Como resultado, tal vez haya cambiado algo, y simplemente lo perdí porque ya no me interesa ... pero lo dudo.