Tengo una aplicación Cocoa (Mac OS X SDK 10.7) que está realizando algunos procesos a través de Grand Central Dispatch (GCD). Estos procesos están manipulando algunos datos básicos NSManagedObjects (no basados en documentos) de una manera que creo que es segura para subprocesos (creando un nuevo managedObjectContext para usar en este hilo).Terminación agraciada de NSApplication con Core Data y Grand Central Dispatch (GCD)
El problema que tengo es cuando el usuario intenta salir de la aplicación mientras la cola de distribución aún se está ejecutando.
Se llama al delegado de NSApplication antes de salir realmente.
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
Aparece el error "No se pudieron combinar los cambios". Lo cual es algo esperado ya que todavía hay operaciones que se realizan a través de los diferentes managedObjectContext. Luego me presentan el NSAlert de la plantilla que se genera con una aplicación de datos básicos.
En el Threading Programming Guide hay una sección llamada "Tenga cuidado con los comportamientos de subprocesos en el tiempo de salida" que alude al método replyToApplicationShouldTerminate:. Estoy teniendo un pequeño problema para implementar esto.
Lo que me gustaría es que mi aplicación complete el procesamiento de los artículos en cola y luego termine sin presentar un mensaje de error al usuario. También sería útil actualizar la vista o usar una hoja para que el usuario sepa que la aplicación está realizando alguna acción y finalizará cuando se complete la acción.
¿Dónde y cómo implementaría este comportamiento?
Solución: Así que tuve algunos problemas diferentes aquí.
tuve bloques que se accede a los datos fundamentales en un
dispatch_queue
prevención de mi solicitud de terminación de gracia.Cuando traté de agregar un nuevo artículo a dispatch_queue se inició una nueva instancia de dispatch_queue en un nuevo hilo.
Lo que hice para solucionar este fue el uso NSNotificationCenter
en mi AppDelegate
(donde estaba siendo llamado (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
En el código de la plantilla que Core de datos genera agregar lo siguiente:.
// Customize this code block to include application-specific recovery steps.
if (error) {
// Do something here to add queue item in AppController
[[NSNotificationCenter defaultCenter] postNotificationName:@"TerminateApplicationFromQueue" object:self];
return NSTerminateLater;
}
Luego, en AppController
añadir una observador de la notificación (he añadido esto a awakeFromNib
):
- (void)awakeFromNib {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(terminateApplicationFromQueue:) name:@"TerminateApplicationFromQueue" object:nil];
// Set initial state of struct that dispatch_queue checks to see if it should terminate the application.
appTerminating.isAppTerminating = NO;
appTerminating.isTerminatingNow = NO;
}
también he creado un struct
que se puede verificar para ver si el usuario desea finalizar la aplicación. (Establecí el estado inicial de la estructura en awakeFromNib
arriba). Coloque el struct
después de sus declaraciones @synthesize
:
struct {
bool isAppTerminating;
bool isTerminatingNow;
} appTerminating;
ya por la prolongada dispatch_queue
que impide la aplicación de la terminación de gracia.Cuando inicialmente creo este dispatch_queue
, se usa un ciclo for para agregar los elementos que necesitan actualización. Después de este bucle se ejecuta, he clavado en otro elemento de cola que se compruebe la struct
para ver si la aplicación debe terminar:
// Additional queue item block to check if app should terminate and then update struct to terminate if required.
dispatch_group_async(refreshGroup, trackingQueue, ^{
NSLog(@"check if app should terminate");
if (appTerminating.isAppTerminating) {
NSLog(@"app is terminating");
appTerminating.isTerminatingNow = YES;
}
});
dispatch_release(refreshGroup);
Y el método que se llamará cuando se recibe la notificación:
- (void)terminateApplicationFromQueue:(NSNotification *)notification {
// Struct to check against at end of dispatch_queue to see if it should shutdown.
if (!appTerminating.isAppTerminating) {
appTerminating.isAppTerminating = YES;
dispatch_queue_t terminateQueue = dispatch_queue_create("com.example.appname.terminate", DISPATCH_QUEUE_SERIAL); // or NULL
dispatch_group_t terminateGroup = dispatch_group_create();
dispatch_group_async(terminateGroup, terminateQueue, ^{
NSLog(@"termination queued until after operation is complete");
while (!appTerminating.isTerminatingNow) {
// add a little delay before checking termination status again
[NSThread sleepForTimeInterval:0.5];
}
NSLog(@"terminate now");
[NSApp replyToApplicationShouldTerminate:YES];
});
dispatch_release(terminateGroup);
}
}
Solo para aclarar: ¿estás regresando? 'NSTerminateLater' de' applicationShouldTerminate: ', como se menciona en' replyToApplicationShouldTerminate: 'docs? –
No, yo no. No estaba seguro de dónde agregar esto. –
¿Qué devuelve, entonces? –