22

Estoy utilizando Grand Central Dispatch (GCD) en mi aplicación para realizar trabajos pesados. La aplicación está utilizando Core-Data para fines de almacenamiento de datos. Aquí está mi escenario (junto con la pregunta relevante):Grand Central Dispatch (GCD) con CoreData

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], fetchResultsController métodos de delegado se denominan de forma automática. 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 el NSManagedObject pueden tardar de 2 a 3 segundos. Por favor avise.

Respuesta

17

Como usted probablemente sabe o ha notado debe llevar a cabo operaciones de interfaz de usuario en el hilo principal. Como mencionas, cuando guardas la actualización de UI se lleva a cabo. Puede resolver esto anidando una llamada al 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 deblockSelfes evitar la creación accidentalmente referencia ciclos. (Practical blocks)

59

Existe una regla de oro cuando se trata de Datos centrales: un contexto de objeto administrado por subproceso. 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 ello se crea un nuevo contexto y darle el mismo almacén persistente como su marco principal:

NSManagedObjectContext *backgroundContext = [[[NSManagedObjectContext alloc] init] autorelease]; 
[backgroundContext setPersistentStoreCoordinator:[mainContext persistentStoreCoordinator]]; 

realizar operaciones lo que tenga que hacer, a continuación, cuando se guarda este nuevo contexto que necesita para manejar la notificación de guardar y fusionar los cambios en su contexto principal con el mensaje mergeChangesFromContextDidSaveNotification:. El código debería ser algo como esto:

/* 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]; 

manipulación del aviso guardar y fusión es importante de lo contrario su principal UI/contexto no verá los cambios realizados. 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 objeto objectID a la operación y recuperar una instancia de NSManagedObject del nuevo contexto usando existingObjectWithID:. Así que algo 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]]; 
+1

Entonces, lo que usted dice es que en mi caso, en lugar de usar fetchedResultsController, debería crear un nuevo contexto de objeto administrado (backgro undManagedObjectContext), busque el objeto gestionado requerido, realice las operaciones requeridas, actualice el objeto gestionado, guarde este contexto de objeto gestionado (backgroundManagedObjectContext) y fusione los cambios para reflejarlos en el contexto principal del objeto gestionado. Esto hará que mi vida sea bastante maldita miserable. – Mustafa

+0

Aún puede usar el controlador de resultados obtenidos para mostrar y actualizar cosas en su UI. El código que te he mostrado es todo lo que se requiere para realizar cualquier operación que necesites en un contexto separado, nada más debería cambiar. –

+0

Hay formas de evitar esta regla de oro: https://github.com/adam-roth/coredata-threadsafe. – aroth

0

Desde Núcleo de datos requiere una Managed Object Context por hilo, una posible solución sería la de realizar un seguimiento de un contexto por hilo en un gestor global, a continuación, realizar un seguimiento de guardar las notificaciones y propagar a todas las discusiones:

Suponiendo :

@property (nonatomic, strong) NSDictionary* threadsDictionary; 

Aquí es cómo conseguir 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 momento en el método init de su administrador mundial (he usado un producto único):

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundContextDidSave:)             name:NSManagedObjectContextDidSaveNotification             object:nil]; 

Luego de recibir guardar las notificaciones y propagar a todos los demás contexto de objetos gestionados:

- (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 se eliminaron para mayor claridad)

Cuestiones relacionadas