GCD, hilos, flujo de progtwigs y actualización de la interfaz de usuario

Me está costando trabajo encontrar la manera de juntar todo esto. Tengo una aplicación de resolución de rompecabezas en el mac. Usted ingresa al acertijo, presiona un botón, y mientras está tratando de encontrar la cantidad de soluciones, movimientos mínimos y tal me gustaría mantener la UI actualizada. Luego, cuando termine de calcular, vuelva a habilitar el botón y cambie el título.

A continuación se muestra un código de muestra del selector de botones y la función de resolución: (Tenga en cuenta que copié / pegué de Xcode para que falten {} o algunos otros errores tipográficos … pero debería darle una idea de lo que estoy diciendo ‘ estoy tratando de hacer

Básicamente, el usuario presiona un botón, ese botón está HABILITADO = NO, función llamada para calcular el rompecabezas. Mientras se calcula, mantenga las tags UI actualizadas con los datos de movimientos / solución. Luego, una vez que termina de calcular el rompecabezas, el botón está HABILITADO = SÍ;

Llamado cuando se presiona el botón:

- (void) solvePuzzle:(id)sender{ solveButton.enabled = NO; solveButton.title = @"Working . . . ."; // I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state. [self performSelectorInBackground:@selector(createTreeFromNode:) withObject:rootNode]; // I've tried to use GCD but similar issue and can't get UI updated. //dispatch_queue_t queue = dispatch_queue_create("com.gamesbychris.createTree", 0); //dispatch_sync(queue, ^{[self createTreeFromNode:rootNode];}); } // Need to wait here until createTreeFromNode is finished. solveButton.enabled=YES; if (numSolutions == 0) { solveButton.title = @"Not Solvable"; } else { solveButton.title = @"Solve Puzzle"; } } 

Necesita ejecutarse en segundo plano para que la interfaz de usuario se pueda actualizar:

 -(void)createTreeFromNode:(TreeNode *)node { // Tried using GCD dispatch_queue_t main_queue = dispatch_get_main_queue(); ...Create Tree Node and find Children Code... if (!solutionFound){ // Solution not found yet so check other children by recursion. [self createTreeFromNode:newChild]; } else { // Solution found. numSolutions ++; if (maxMoves < newChild.numberOfMoves) { maxMoves = newChild.numberOfMoves; } if (minMoves  newChild.numberOfMoves) { solutionNode = newChild; minMoves = newChild.numberOfMoves; // Update UI on main Thread dispatch_async(main_queue, ^{ minMovesLabel.stringValue = [NSString stringWithFormat:@"%d",minMoves]; numSolutionsLabel.stringValue = [NSString stringWithFormat:@"%d",numSolutions]; maxMovesLabel.stringValue = [NSString stringWithFormat:@"%d",maxMoves]; }); } 

A continuación, muestras de GCD y performSelectorInBackground. Pero primero, veamos tu código.

No puedes esperar donde quieras en el código de arriba. Aquí está el código que tenía. Donde dices esperar en el comentario es incorrecto. Ver donde he añadido NO.

 - (void) solvePuzzle:(id)sender{ solveButton.enabled = NO; solveButton.title = @"Working . . . ."; // I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state. [self performSelectorInBackground:@selector(createTreeFromNode:) withObject:rootNode]; // NO - do not wait or enable here. // Need to wait here until createTreeFromNode is finished. solveButton.enabled=YES; } 

Un bucle de mensaje UI se ejecuta en el hilo principal que mantiene la UI en funcionamiento. se llama a solvePuzzle en el hilo principal, por lo que no puedes esperar; bloqueará la interfaz de usuario. Tampoco puede volver a configurar el botón como habilitado; el trabajo aún no se ha realizado.

Es el trabajo de la función del trabajador en el subproceso de fondo para hacer el trabajo y luego, cuando está listo para actualizar la interfaz de usuario. Pero no puede actualizar la interfaz de usuario desde un hilo de fondo. Si no está utilizando bloques y usa performSelectInBackground, cuando termine, llame a performSelectorOnMainThread, que llama a un selector para actualizar su UI.

Ejemplo de performSelectorInBackground:

En este fragmento, tengo un botón que invoca el trabajo de larga duración, una etiqueta de estado, y agregué un control deslizante para mostrar que puedo mover el control deslizante mientras el trabajo de bg está hecho.

 // on click of button - (IBAction)doWork:(id)sender { [[self feedbackLabel] setText:@"Working ..."]; [[self doWorkButton] setEnabled:NO]; [self performSelectorInBackground:@selector(performLongRunningWork:) withObject:nil]; } - (void)performLongRunningWork:(id)obj { // simulate 5 seconds of work // I added a slider to the form - I can slide it back and forth during the 5 sec. sleep(5); [self performSelectorOnMainThread:@selector(workDone:) withObject:nil waitUntilDone:YES]; } - (void)workDone:(id)obj { [[self feedbackLabel] setText:@"Done ..."]; [[self doWorkButton] setEnabled:YES]; } 

Muestra de GCD:

 // on click of button - (IBAction)doWork:(id)sender { [[self feedbackLabel] setText:@"Working ..."]; [[self doWorkButton] setEnabled:NO]; // async queue for bg work // main queue for updating ui on main thread dispatch_queue_t queue = dispatch_queue_create("com.sample", 0); dispatch_queue_t main = dispatch_get_main_queue(); // do the long running work in bg async queue // within that, call to update UI on main thread. dispatch_async(queue, ^{ [self performLongRunningWork]; dispatch_async(main, ^{ [self workDone]; }); }); } - (void)performLongRunningWork { // simulate 5 seconds of work // I added a slider to the form - I can slide it back and forth during the 5 sec. sleep(5); } - (void)workDone { [[self feedbackLabel] setText:@"Done ..."]; [[self doWorkButton] setEnabled:YES]; } 
  dispatch_queue_t backgroundQueue; backgroundQueue = dispatch_queue_create("com.images.bgqueue", NULL); - (void)process { dispatch_async(backgroundQueue, ^(void){ //background task [self processHtml]; dispatch_async(main, ^{ // UI updates in main queue [self workDone]; }); }); }); } 

En general, cualquier trabajo que se envíe a una cola en segundo plano debe seguir este patrón de código:

 dispatch_queue_t queue = dispatch_queue_create("com.myappname", 0); __weak MyClass *weakSelf = self; //must be weak to avoid retain cycle //Assign async work dispatch_async(queue, ^{ [weakSelf doWork]; dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf workDone]; }); }); queue = nil; //Using ARC, we nil out. Block always retains the queue. 

Nunca olvides:

1 – la variable de cola anterior es un objeto contado de referencia , porque es una cola privada, no global. Por lo tanto, es retenido por el bloque que se está ejecutando dentro de esa cola. Hasta que esta tarea se complete, no se libera.

2 – Cada cola tiene su propia stack que será asignada / desasignada como parte de la operación recursiva. Solo tiene que preocuparse por las variables de los miembros de la clase que cuentan como referencia contada (fuerte, retención, etc.) a las que se accede como parte de doWork arriba.

3 – Al acceder a los vars contados de referencia dentro de la operación de cola de fondo, debe hacerlos seguros para subprocesos, dependiendo de los casos de uso en su aplicación. Los ejemplos incluyen escrituras en objetos tales como cadenas, matrices, etc. Esas escrituras deben estar encapsuladas dentro de la palabra clave @synchronized para asegurar el acceso seguro a subprocesos.

@synchronized asegura que ningún otro subproceso puede tener acceso al recurso que protege, mientras se ejecuta el bloque que encapsula.

 @synchronized(myMutableArray) { //operation } 

En el bloque de código anterior, no se permiten myMutableArray en myMutableArray dentro del bloque @synchronized por ningún otro hilo.