2010-03-24 6 views
5

Tengo un bucle while, que se ejecuta durante varios segundos y es por eso que deseo actualizar una barra de progreso (NSProgressIndicator) durante ese proceso, pero se actualiza solo una vez después de que el ciclo ha finalizado. Lo mismo ocurre si quiero actualizar un texto de etiqueta, por cierto.¿Cómo actualizo una barra de progreso en Cocoa durante un ciclo prolongado?

Creo que mi bucle impide que sucedan otras cosas de esa aplicación. Debe haber otra técnica. ¿Tiene esto que ver con hilos o algo? ¿Estoy en el camino correcto? ¿Puede alguien darme un ejemplo simple, cómo "optimizar" mi aplicación?

Mi aplicación es una aplicación Cocoa (Xcode 3.2.1) con estos dos métodos en mi Example_AppDelegate.m:

 
// This method runs when a start button is clicked. 
- (IBAction)startIt:(id)sender { 
    [progressbar setDoubleValue:0.0]; 
    [progressbar startAnimation:sender]; 
    running = YES; // this is a instance variable 

    int i = 0; 
    while (running) { 
     if (i++ >= processAmount) { // processAmount is something like 1000000 
      running = NO; 
      continue; 
     } 

     // Update progress bar 
     double progr = (double)i/(double)processAmount; 
     NSLog(@"progr: %f", progr); // Logs values between 0.0 and 1.0 
     [progressbar setDoubleValue:progr]; 
     [progressbar needsDisplay]; // Do I need this? 

     // Do some more hard work here... 
    } 
} 

// This method runs when a stop button is clicked, but as long 
// as -startIt is busy, a click on the stop button does nothing. 
- (IBAction)stopIt:(id)sender { 
    NSLog(@"Stop it!"); 
    running = NO; 
    [progressbar stopAnimation:sender]; 
} 

Estoy muy nuevo en Objective-C, Cacao y aplicaciones con una interfaz de usuario. Muchas gracias por cualquier respuesta útil.

Respuesta

15

Si usted está construyendo para Snow Leopard, la solución más fácil es en mi opinión a utilizar bloques y Grand Central Dispatch.

El siguiente código muestra cómo se vería su método startIt: al usar GCD.

Su método stopIt: debería funcionar bien tal como lo escribió. La razón por la que no estaba funcionando antes es que los eventos del mouse suceden en el hilo principal y, por lo tanto, el botón no respondió porque estaba trabajando en el hilo principal. Este problema debería haberse resuelto ahora ya que el trabajo se ha puesto en un hilo diferente ahora con GCD. Pruebe el código y, si no funciona, hágamelo saber y veré si cometí algunos errores.

// This method runs when a start button is clicked. 
- (IBAction)startIt:(id)sender { 

    //Create the block that we wish to run on a different thread. 
    void (^progressBlock)(void); 
    progressBlock = ^{ 

    [progressbar setDoubleValue:0.0]; 
    [progressbar startAnimation:sender]; 
    running = YES; // this is a instance variable 

    int i = 0; 
    while (running) { 
     if (i++ >= processAmount) { // processAmount is something like 1000000 
      running = NO; 
      continue; 
     } 

     // Update progress bar 
     double progr = (double)i/(double)processAmount; 
     NSLog(@"progr: %f", progr); // Logs values between 0.0 and 1.0 

     //NOTE: It is important to let all UI updates occur on the main thread, 
     //so we put the following UI updates on the main queue. 
     dispatch_async(dispatch_get_main_queue(), ^{ 
      [progressbar setDoubleValue:progr]; 
      [progressbar setNeedsDisplay:YES]; 
     }); 

     // Do some more hard work here... 
    } 

    }; //end of progressBlock 

    //Finally, run the block on a different thread. 
    dispatch_queue_t queue = dispatch_get_global_queue(0,0); 
    dispatch_async(queue,progressBlock); 
} 
+0

Muchas gracias, voy a dar Es un intento este fin de semana! – Nick

+0

Eso es lo que estaba buscando.Funciona bien y esto me da un buen punto de partida para aprender más al respecto. ¡Gracias! – Nick

+0

Gracias por esta Enchilada! ¡Acabo de aprender algo que no sabía! Muy emocionado de descubrir sobre bloques y GCD !! –

2

Creo que mi bucle impide que sucedan otras cosas de esa aplicación.

Correcto. Necesitas romper esto de alguna manera.

Una forma sería un temporizador, con la reducción de la cola un poco a la vez en la devolución de llamada del temporizador. Otro sería ajustar el código para manejar un elemento en una subclase de NSOperation, y crear instancias de esa clase (operaciones) y ponerlas en un NSOperationQueue.

¿Esto tiene que ver con los hilos o algo así?

No necesariamente

. Las NSOperations se ejecutan en hilos, pero NSOperationQueue manejará engendrar el hilo por usted. Un temporizador es una solución de un solo subproceso: cada temporizador se ejecuta en el hilo que lo programa. Eso puede ser una ventaja o una desventaja, tú decides.

Ver the threads section of my intro to Cocoa para más detalles.

3

Puede probar este código ..

[progressbar setUsesThreadedAnimation:YES]; 
2

Esto funcionó para mí, que es una combinación de respuestas de otros que no parecen funcionar (al menos para mí) por su propia cuenta:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ 
    //do something 
    dispatch_async(dispatch_get_main_queue(), ^{ 
    progressBar.progress = (double)x/(double)[stockList count];    
    }); 
    //do something else 
}); 
Cuestiones relacionadas