iphone - dispatchqueue - ios gcd
Grand Central Dispatch(GCD) con CoreData (3)
Como Core Data requiere un contexto de objeto gestionado por hilo, una posible solución sería rastrear un contexto por subproceso en un administrador global, luego rastrear las notificaciones de guardado y propagar a todos los subprocesos:
Asumiendo:
@property (nonatomic, strong) NSDictionary* threadsDictionary;
Aquí se muestra cómo obtener el objeto gestionado (por hilo):
- (NSManagedObjectContext *) managedObjectContextForThread {
// Per thread, give one back
NSString* threadName = [NSString stringWithFormat:@"%d",[NSThread currentThread].hash];
NSManagedObjectContext * existingContext = [self.threadsDictionary objectForKey:threadName];
if (existingContext==nil){
existingContext = [[NSManagedObjectContext alloc] init];
[existingContext setPersistentStoreCoordinator: [self persistentStoreCoordinator]];
[self.threadsDictionary setValue:existingContext forKey:threadName];
}
return existingContext;
}
En algún punto del método init de su gerente global (utilicé un singleton):
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:nil];
Luego, para recibir notificaciones de guardado y propagar a todos los demás objetos de contexto administrados:
- (void)backgroundContextDidSave:(NSNotification *)notification {
/* Make sure we''re on the main thread when updating the main context */
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:NO];
return;
}
/* merge in the changes to the main context */
for (NSManagedObjectContext* context in [self.threadsDictionary allValues]){
[context mergeChangesFromContextDidSaveNotification:notification];
}
}
(algunos otros métodos fueron eliminados para mayor claridad)
Estoy usando Grand Central Dispatch (GCD) en mi aplicación para levantar objetos pesados. La aplicación está utilizando Core-Data para fines de almacenamiento de datos. Aquí está mi escenario (junto con la pregunta relevante):
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_queue_t request_queue = dispatch_queue_create("com.app.request", NULL);
dispatch_async(request_queue, ^{
MyNSManagedObject *mObject = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
// …
// <heavy lifting>
// …
// …
// <update mObject>
// …
[self saveManagedObjectContext];
});
Como resultado de [self saveManagedObjectContext]
, los métodos de delegado fetchResultsController
se llaman automáticamente. En consecuencia, la lógica de actualización de la interfaz de usuario patea.
Ahora mi pregunta es, ¿necesito usar main_queue
para -saveManagedObjectContext
? ¿Debo realizar todas las operaciones en mi NSManagedObject
en main_queue
? Algunas de las operaciones que actualizan NSManagedObject
pueden tardar 2-3 segundos. Por favor avise.
Como probablemente sepa o haya notado, debe realizar operaciones de IU en el hilo principal. Como mencionas, cuando guardas la actualización de UI se lleva a cabo. Puede resolver esto anidando una llamada a dispatch_sync
en el hilo principal.
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_queue_t request_queue = dispatch_queue_create("com.app.request", NULL);
__block __typeof__(self) blockSelf = self;
dispatch_async(request_queue, ^{
MyNSManagedObject *mObject = [blockSelf.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
// update and heavy lifting...
dispatch_sync(main_queue, ^{
[blockSelf saveManagedObjectContext];
});
});
El uso de blockSelf
es evitar la creación accidental de ciclos de referencia. ( Bloques prácticos )
Hay una regla de oro cuando se trata de Datos centrales: un contexto de objeto gestionado por hilo. Los contextos de objeto administrado no son seguros para subprocesos, por lo que si está trabajando en una tarea en segundo plano, utilice el hilo principal para evitar conflictos con operaciones de interfaz de usuario o cree un contexto nuevo para realizar el trabajo. Si el trabajo va a tomar unos segundos luego debe hacer esto último para evitar que su UI se bloquee.
Para hacer esto, crea un nuevo contexto y le da la misma tienda persistente que su contexto principal:
NSManagedObjectContext *backgroundContext = [[[NSManagedObjectContext alloc] init] autorelease];
[backgroundContext setPersistentStoreCoordinator:[mainContext persistentStoreCoordinator]];
Haga las operaciones que necesite, luego, cuando guarde ese nuevo contexto, necesita manejar la notificación de guardado y fusionar los cambios en su contexto principal con el mensaje mergeChangesFromContextDidSaveNotification:
. El código debería verse más o menos así:
/* Save notification handler for the background context */
- (void)backgroundContextDidSave:(NSNotification *)notification {
/* Make sure we''re on the main thread when updating the main context */
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:NO];
return;
}
/* merge in the changes to the main context */
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
/* ... */
/* Save the background context and handle the save notification */
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:backgroundContext];
[backgroundContext save:NULL];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:syncContext];
Manejar la notificación y la combinación de guardar es importante; de lo contrario, su interfaz de usuario / contexto principal no verá los cambios que realizó. Al fusionarse, su fetchResultsController principal obtendrá eventos de cambio y actualizará su UI como era de esperar.
Otra cosa importante a tener en cuenta es que las instancias de NSManagedObject solo se pueden usar en el contexto desde el que se obtuvieron. Si su operación necesita una referencia a un objeto, debe pasar el objectID
de objeto del objeto a la operación y volver a buscar una instancia de NSManagedObject desde el nuevo contexto utilizando existingObjectWithID:
Entonces algo así como:
/* This can only be used in operations on the main context */
MyNSManagedObject *objectInMainContext =
[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
/* This can now be used in your background context */
MyNSManagedObject *objectInBackgroundContext =
(MyNSManagedObject *) [backgroundContext existingObjectWithID:[objectInMainContext objectID]];