objective c - ¿Qué quiere decir Apple cuando dicen que un NSManagedObjectContext es propiedad del hilo o la cola que lo creó?
objective-c multithreading (2)
Parece que en noviembre, Apple actualizó tanto la Referencia de Clase NSManagedObjectContext como los documentos de la Guía de Programación de Datos Básicos para bendecir explícitamente las Colas de Despacho GCD en serie y NSOperationQueues como mecanismos aceptables para sincronizar el acceso a un NSManagedObjectContext
. Pero su consejo parece ambiguo y posiblemente contradictorio, y quiero asegurarme de haberlo entendido correctamente.
Anteriormente, la sabiduría aceptada parecía ser que solo se podía acceder a NSManagedObjectContext
desde el hilo que lo creaba, y que usar una cola en serie para la sincronización no era suficiente; aunque las colas en serie solo realizan una operación a la vez, estas operaciones se pueden programar potencialmente en diferentes subprocesos, y a un MOC no le gusta eso.
Pero ahora, desde la guía de programación, tenemos:
Puede usar subprocesos, colas de operaciones en serie o colas de distribución para concurrencia. En aras de la concisión, este artículo usa "hilo" para referirse a cualquiera de estos.
Hasta ahora, todo bien (aunque su combinación de hilos y colas no es útil). Entonces, puedo usar con seguridad un solo contexto por cola (serie), en lugar de uno por operación / bloque, ¿verdad? Apple incluso tiene una representación visual de esto en las sesiones de WWDC de Core Data.
Pero ... ¿dónde creas el contexto para la cola? En la documentación NSManagedObjectContext
, estado de Apple:
[Un contexto] supone que el propietario predeterminado es el hilo o la cola que lo asignó, esto está determinado por el hilo que llama a su método init. Por lo tanto, no debe inicializar un contexto en un hilo y pasarlo a un hilo diferente.
Entonces ahora tenemos la idea de que un NSManagedObjectContext
necesite saber quién es su propietario. Supongo que esto significa que la primera operación que se ejecutará en la cola debe crear el MOC y guardar una referencia para el resto de las operaciones.
¿Es esto correcto? La única razón por la que dudo es que el artículo NSManagedObjectContext
continúa diciendo:
En su lugar, debe pasar una referencia a un coordinador de tienda persistente y hacer que el hilo / cola de recepción cree un nuevo contexto derivado de eso. Si usa NSOperation, debe crear el contexto en main (para una cola en serie) o start (para una cola concurrente).
Apple ahora parece estar combinando operaciones con las colas que programan su ejecución. Esto me hace entrar en razón, y me hace preguntar si realmente quieren que simplemente crees un nuevo MOC para cada operación después de todo. ¿Qué me estoy perdiendo?
El NSManagedObjectContext y cualquier objeto administrado asociado con él debe estar anclado a un solo actor (hilo, cola serializada, NSOperationQueue con concurrencia máxima = 1).
Este patrón se denomina confinamiento o aislamiento de hilo. No hay una gran frase para (hilo | cola serializada || NSOperationQueue con concurrencia máxima = 1) por lo que la documentación continúa diciendo "solo usaremos ''thread'' para el resto del documento Core Data cuando nos referimos cualquiera de esas 3 formas de obtener un flujo de control serializado "
Si crea un MOC en un subproceso y luego lo usa en otro, ha violado el confinamiento del subproceso al exponer la referencia del objeto MOC a dos subprocesos. Sencillo. No lo hagas No cruce las corrientes.
Llamamos NSOperation explícitamente porque a diferencia de los hilos y GCD, tiene este extraño problema donde -init se ejecuta en el hilo que crea el NSOperation pero -principal se ejecuta en el hilo que ejecuta el NSOperation. Tiene sentido si bizcas correctamente, pero no es intuitivo. Si crea su MOC en - [NSOperation init], entonces NSOperation infructivamente violará el confinamiento del hilo antes de que su método principal incluso se ejecute y usted reciba una manguera.
Desalentamos / desaprobamos activamente el uso de MOC y subprocesos de otras maneras. Aunque teóricamente es posible hacer lo que bbum menciona, nadie nunca tuvo ese derecho. Todo el mundo tropezó, olvidó una llamada necesaria para "bloquear" en 1 lugar, "¿Inicial corre donde?", O de otra manera se sobresaltó. Con los grupos de liberación automática y el bucle de evento de la aplicación y el administrador de deshacer y las vinculaciones de cacao y KVO, existen muchas maneras para que un hilo retenga una referencia a un MOC después de que haya tratado de pasarlo a otro lugar. Es mucho más difícil de lo que incluso los desarrolladores avanzados de Cocoa imaginan hasta que comiencen a depurar. Entonces esa no es una API muy útil.
La documentación cambió para aclarar y enfatizar el patrón de confinamiento del hilo como la única manera sensata de proceder. Debería considerar intentar ser más sofisticado utilizando -bloqueo y -desbloqueo en NSManagedObjectContext para ser (a) imposible y (b) obsoleto de hecho. No está literalmente en desuso porque el código funciona tan bien como siempre. Pero el código que lo usa es incorrecto.
Algunas personas crearon MOCs en 1 hilo y los pasaron a otro sin llamar -lock. Eso nunca fue legal. El hilo que creó el MOC siempre ha sido el propietario predeterminado del MOC. Esto se convirtió en un problema más frecuente para los MOC creados en el hilo principal. Los MOC de subproceso principal interactúan con el bucle de evento principal de la aplicación para deshacer, administrar la memoria y algunas otras razones. En 10.6 e iOS 3, los MOC toman una ventaja más agresiva de ser propiedad del hilo principal.
Aunque las colas no están vinculadas a subprocesos específicos, si crea un MOC en el contexto de una cola, ocurrirán las cosas correctas. Su obligación es seguir la API pública.
Si la cola está serializada, puede compartir el MOC con los bloques siguientes que se ejecutan en esa cola.
Por lo tanto, no exponga un NSManagedObjectContext * a más de un hilo (actor, etc.) bajo ninguna circunstancia. Hay una ambigüedad. Puede pasar NSNotification * de la notificación didSave al método MOC''s -mergeChangesFromContextDidSaveNotification: de otro thread.
- Ben
Parece que lo hiciste bien. Si está utilizando subprocesos, el hilo que desea el contexto debe crearlo. Si usa colas, la cola que desea el contexto debe crearla, probablemente como el primer bloque para ejecutar en la cola. Parece que la única parte confusa es el tema de NSOperations. Creo que la confusión allí es NSOperations no proporciona ninguna garantía sobre qué subproceso / cola subyacente se ejecutan, por lo que puede no ser seguro para compartir un MOC entre las operaciones, incluso si se ejecutan todos en el mismo NSOperationQueue. Una explicación alternativa es que solo confunde la documentación.
En resumen:
- Si está utilizando subprocesos, cree el MOC en el subproceso que lo quiera
- Si está utilizando GCD, cree el MOC en el primer bloque ejecutado en su cola serie
- Si está utilizando NSOperation, cree el MOC dentro de NSOperation y no lo comparta entre las operaciones. Esto puede ser un poco paranoico, pero NSOperation no garantiza en qué subproceso / cola subyacente se ejecuta.
Editar : Según el bbum, el único requisito real es el acceso debe ser serializado. Esto significa que puede compartir un MOC en NSOperations siempre que todas las operaciones se hayan agregado a la misma cola y la cola no permita operaciones concurrentes.