T068 - Progress view esecuzione di task in background iphone Ad essere sincero non credevo di trovar un momento per realizzare due tutorial durante questa estate, limitiamoci a dire che rompersi lo zigomo proprio nel bel mezzo di agosto significa avere un mucchio di tempo libero seduto su un letto d’ospedale. E quale miglior modo per passare il tempo se non scrivendo un paio di tutorial per voi?

Bene in questo primo tutorial volevo parlare delle progress bar e come fare ad aggiornarle mostrando sullo schermo del nostro terminale quanto manca alla conclusione di alcuni task particolarmente dispendiosi in termini di tempo. Può sembrare un lavoro da poco, ma al contrario di quello che inizialmente può sembrare, presenta alcune insidie.

Credo che tutti sapete come creare una view based application e di aggiungere una progress view tramite Interface Builder, ma visto che il tempo non mi manca, ne perderò un po’ per spiegarlo anche a quelli di voi che si sono appena avvicinati alla programmazione per iPhone e magari non hanno ancora acquisito dimestichezza con questo strumento.

Una volta creato tramite Xcode il nostro progetto per un’applicazione iPhone view based chiamata, per esempio, ProgressBar (ok lo so di non avere molta fantasia nei nomi :) ) apriamo il nostro ProgressBarViewController.h e aggiungiamo un riferimento che ci permetterà di accedere alla progress view. A questo scopo modifichiamo il file come segue andando quindi ad aggiungere un puntatore a quella che sarà la nostra progress view:

1
2
3
4
5
@interface ProgressBarViewController : UIViewController {
	IBOutlet UIProgressView *progressBar;
}
 
@end

Fatto questo non dobbiamo far altro che aprire con Interface Builder il file ProgressBarViewController.xib, aggiungere una progress view prendendola dalla library, trascinarla nella posizione in cui vogliamo metterla e collegarla alla variabile che abbiamo appena creato nel modo che possiamo vedere in figura:


Operazione da eseguire in Interface Builder

Ora possiamo passare al tutorial vero e proprio. Cominciamo col creare una funzione che simulerà una massiccia elaborazione di dati. Ipotizziamo per esempio 100 task della durata di 0.1 secondi ciascuno. La nostra funzione apparirà come segue:

1
2
3
4
5
6
7
8
- (void)heavyDataProcessing {
  NSInteger numberOfTasks = 100;
    for (NSInteger tasksDone = 0; tasksDone <= numberOfTasks; tasksDone++) {
      [NSThread sleepForTimeInterval:0.1];
      NSNumber *progress = [[NSNumber alloc] initWithFloat:((float)tasksDone / (float)numberOfTasks)];
      [progress release];
    }
}

Per ora ci limitiamo a memorizzare il valore di progresso che abbiamo raggiunto nell’esecuzione dei vari task in un NSNumber perché prima di scrivere le istruzioni per eseguire il nostro heavyDataProcessing e di modificare la progress view abbiamo bisogno di un paio di nozioni che vado adesso a spiegare nella maniera più semplice possibile.

Nel thread principale viene eseguito il main run loop che si preoccupa di catturare gli eventi (i più comuni sono i tocchi sullo schermo). Quello che interessa a noi però è che anche la modifica al valore che la progress view deve visualizzare rappresenta un evento. Una volta che il main run loop riceve un evento questo lo passa all’UIResponder più opportuno (ovvero la nostra UIProgressView, che eredita da UIView che a sua volta eredita da UIResponder) che lo gestirà, nel nostro caso verrà ridisegnando la progress view con il valore aggiornato. Ora se noi invochiamo il metodo heavyDataProcessing normalmente, questo verrà eseguito nel main thread. Verrà quindi bloccata l’esecuzione del run loop per permettere l’esecuzione del metodo. Questo fatto che a prima vista ci sembra una stupidata ha un effetto piuttosto micidiale. Niente run loop significa niente gestione degli eventi che nel nostro caso si traduce in nessun update del valore visualizzato dalla nostra progress bar (che verrà aggiornata solo al termine della funzione). Per ovviare a questo problema ci basta ricorrere ad un piccolo espediente: eseguiamo la funzione per il data processing in un thread secondario chiamato background thread limitandoci ad eseguire nel main thread solo la semplice e di rapida esecuzione funzione per l’update della progress bar.
Vediamo ora come.

Cominciamo invocando il metodo nel Background thread. Per far ciò ridefiniamo il metodo viewDidLoad (in questo modo siamo anche sicuri che la progress view è stata creata).

1
2
3
4
- (void)viewDidLoad {
   [self performSelectorInBackground:@selector(heavyDataProcessing)
                          withObject:nil];
}

Ora la nostra funzione heavyDataProcessing verrà eseguita in un thread separato. Per poter far funzionare il tutto correttamente, ci mancano ancora due passaggi:

  1. Scrivere la funzione che aggiornerà la progress view (da eseguire nel main thread).
  2. Aggiungere la chiamata a questa funzione nella heavyDataProcessing.

Cominciamo con scrivere la funzione:

1
2
3
- (void)updateProgressBar:(NSNumber *)progress {
	progressBar.progress = [progress floatValue];
}

Si tratta di una semplicissima funzione che prende come parametro il valore attuale di progresso della progress bar e glielo assegna.

Andiamo ora a modificare la funzione heavyDataProcessing come segue:

1
2
3
4
5
6
7
8
9
- (void)heavyDataProcessing {
   NSInteger numberOfTasks = 100;
   for (NSInteger tasksDone = 0; tasksDone <= numberOfTasks; tasksDone++) {
      [NSThread sleepForTimeInterval:0.1];
      NSNumber *progress = [[NSNumber alloc] initWithFloat:((float)tasksDone / (float)numberOfTasks)];
      [self performSelectorOnMainThread:@selector(updateProgressBar:) withObject:progress waitUntilDone:NO];
      [progress release];
   }
}

Come possiamo vedere ci siamo limitati ad a aggiungere la riga numero 6 che ci permette di far eseguire la funzione updateProgressBar nel main thread, passando come parametro il nuovo valore da assegnare alla progress bar (valore che abbiamo calcolato e salvato nell’NSNumber).

Perfetto! Ora tutto dovrebbe funzionare a dovere. Premete su Build and Run e godetevi la progress bar che a poco a poco si riempie.

Come tutte le altre volte, anche il codice di questo tutorial lo trovate hostato su Google Code.


T068 - Progress view esecuzione di task in background iphone