Objective-C, cancelar una queue de despacho utilizando un evento UI

Guión:

  • El usuario toca un button pidiendo algún tipo de modificación en la libreta de direcciones.
  • Se llama a un método para iniciar esta modificación y se muestra una vista de alerta.
  • Para mostrar la vista de alerta y mantener la interfaz de usuario receptiva, utilicé dispatch_queue:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(dispatch_get_main_queue(), ^{ // Show the alert view }); }); 
  • Comience el process de modificación de la libreta de direcciones utilizando:

     dispatch_async(modifyingAddressBookQueue, ^{}); 

Ahora, quiero proporcionarle al usuario la posibilidad de cancelar el process en cualquier momento (por supuesto, antes de save la libreta de direcciones). Entonces, cuando toca el button cancelar en la hoja de alerta, quiero acceder al bloque de envío, establecer algunos BOOL determinados para detener el process y revertir la libreta de direcciones.

¡El problema es que no puedes hacer eso! no puede acceder al bloque y cambiar cualquier variable dentro de él ya que todas las variables se copyn solo una vez. Cualquier cambio de variables dentro del bloque mientras se ejecuta no será visto por el bloque.

Para resumir: ¿Cómo detener una operación en curso utilizando un evento de UI?

Actualizar:

El código para el process:

 - (void) startFixingModification { _fixContacts = YES; __block BOOL cancelled = NO; dispatch_queue_t modifyingAddressBookQueue; modifyingAddressBookQueue = dispatch_queue_create(sModifyingAddressBookQueueIdentifier, NULL); dispatch_async(modifyingAddressBookQueue, ^{ for (NSMutableDictionary *contactDictionary in _contactArray) { if (!cancelled) { break; } i = i + 1; BOOL didFixContact = [self fixNumberInContactDictionary:contactDictionary]; if (!didFixContact) { _fixedNumbers = _fixedNumbers - 1; } dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(dispatch_get_main_queue(), ^{ [self setAlertViewProgress:i]; }); }); } }); cancelledPtr = &cancelled; } 

Código para alertview (mi propio lib) delegado

 - (void) alertViewProgressCancel:(ASAlertViewProgress *)alertView { // This is a private lib. if (cancelledPtr) { NSLog(@"stopping"); *cancelledPtr = YES; } } 

En interfaz, declaro

 BOOL* cancelledPtr; 

Actualización 2:

¡Se está poniendo realmente frustrante! para el siguiente código

 for (NSMutableDictionary *contactDictionary in _contactArray) { NSLog(@"%d", _cancelModification); if (_cancelModification) { break; } } 

si _cancelModification se establece en YES, el ciclo for se rompe y está bien. Una vez que comento la línea NSLog, la _cancelModification se descuida cuando cambia a YES!

Si declara su BOOL usando __block , entonces se puede cambiar fuera de la ejecución del bloque, y el bloque verá el nuevo valor. Consulte la documentation para más detalles.

Un ejemplo:

 @interface SNViewController () { BOOL* cancelledPtr; } @end @implementation SNViewController - (IBAction)start:(id)sender { __block BOOL cancelled = NO; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while (!cancelled) { NSLog(@"running"); sleep(1); } NSLog(@"stopped"); }); cancelledPtr = &cancelled; } - (IBAction)stop:(id)sender { if (cancelledPtr) { NSLog(@"stopping"); *cancelledPtr = YES; } } @end 

Alternativamente, use un ivar en su class para almacenar BOOL. El bloque implícitamente hará una copy de self y accederá al ivar a través de eso. No es necesario __block .

 @interface SNViewController () { BOOL cancelled; } @end @implementation SNViewController - (IBAction)start:(id)sender { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while (!cancelled) { NSLog(@"running"); sleep(1); } NSLog(@"stopped"); }); } - (IBAction)stop:(id)sender { NSLog(@"stopping"); cancelled = YES; } @end 

Enfoque 1

Cree un método personalizado dispatch_async que devuelva un bloque "cancelable".

 // The dispatch_cancel_block_t takes as parameter the "cancel" directive to suspend the block execution or not whenever the block to execute is dispatched. // The return value is a boolean indicating if the block has already been executed or not. typedef BOOL (^dispatch_cancel_block_t)(BOOL cancelBlock); dispatch_cancel_block_t dispatch_async_with_cancel_block(dispatch_queue_t queue, void (^block)()) { __block BOOL execute = YES; __block BOOL executed = NO; dispatch_cancel_block_t cancelBlock = ^BOOL (BOOL cancelled) { execute = !cancelled; return executed == NO; }; dispatch_async(queue, ^{ if (execute) block(); executed = YES; }); return cancelBlock; } - (void)testCancelableBlock { dispatch_cancel_block_t cancelBlock = dispatch_async_with_cancel_block(dispatch_get_main_queue(), ^{ NSLog(@"Block 1 executed"); }); // Canceling the block execution BOOL success1 = cancelBlock(YES); NSLog(@"Block is cancelled successfully: %@", success1?@"YES":@"NO"); // Resuming the block execution // BOOL success2 = cancelBlock(NO); // NSLog(@"Block is resumed successfully: %@", success2?@"YES":@"NO"); } 

Enfoque 2

Definición de una macro para ejecutar un bloque de forma asíncrona si se valida una condición:

 #define dispatch_async_if(queue,condition,block) \ dispatch_async(queue, ^{\ if (condition == YES)\ block();\ }); - (void)testConditionBlock { // Creating condition variable __block BOOL condition = YES; dispatch_async_if(dispatch_get_main_queue(), condition, ^{ NSLog(@"Block 2 executed"); }); // Canceling the block execution condition = NO; // Also, we could use a method to test the condition status dispatch_async_if(dispatch_get_main_queue(), ![self mustCancelBlockExecution], ^{ NSLog(@"Block 3 executed"); }); } 

Intente aplicar el siguiente ejemplo de código a su situación:

 __block UIView * tempView = [[UIView alloc] initWithFrame:CGRectMake(50, 100, 220, 30)]; [tempView setBackgroundColor:[UIColor grayColor]]; [self.view addSubview:tempView]; [tempView release]; __block BOOL cancel = NO; //点击之后就会开始执行这个方法dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ int i = 0; while (i < 1000000000 && cancel == NO) { i++; } NSLog(@"Task end: i = %d", i); //这个不会执行,因为在之前,gcd task已经结束[tempView removeFromSuperview]; }); //1s 之后执行这个方法double delayInSeconds = 1.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ NSLog(@"A GCD Task Start"); cancel = YES; [tempView setBackgroundColor:[UIColor blackColor]]; });