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:
@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:

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:
- (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).
- (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:
- Scrivere la funzione che aggiornerà la progress view (da eseguire nel main thread).
- Aggiungere la chiamata a questa funzione nella heavyDataProcessing.
Cominciamo con scrivere la funzione:
- (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:
- (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.

4 Responses to “T#068 – Progress view ed esecuzione di task in background”
31 Agosto 2010
Tweets that mention T#068 - Progress view ed esecuzione di task in background | devAPP -- Topsy.com[…] This post was mentioned on Twitter by Rynox, devAPP. devAPP said: T#068 – Progress view ed esecuzione di task in background http://bit.ly/9ndSH4 […]
16 Settembre 2010
BrunoOh, quanto mi dispiace per il tuo zigomo! Qualche hanno fa ruppi il polso, 4 ore di lavoro prima di andarmene in ferie 3 settimane, ovvero ESATTAMENTE il tempo di rimuovere il gesso…
Puoi immaginare quanti bagni in piscina ho fatto….. che sfiga…
Tanti auguri di pronta guarigione, recupererai l’hanno prossimo!
PS. Ma si può sapere come hai fatto? Moto, calcio, rissa o hai detto alla morosa che era ingrassata?? :o)
17 Settembre 2010
Luca Di FrancoE’ successo tutto con una classica craniata giocando a calcio. Fosse stata la morosa non me la sarei cavata con così poco 😛
19 Settembre 2011
AleCiao, utilissima guida, grazie.
Ho una variante da sottoporti.
Nel controller di una view ho una var di istanza della mia cSync.
Alla pressione di un pulsante eseguo oSync.exec che impiega una decina di secondi per essere eseguito.
OSync.exec scatena un evento al suo avvio e uno al termine, nel controller della view (suo delegato)
In questi due eventi rendo una progresso bar = no e = yen.
Il problema è che, come hai decritto nell’articolo, fino a quando non è terminata l’esecuzione del metodo oSync.exec non viene eseguito neanche il codice dell’evento start, scatenato all’inizio.
Se non sono stato chiaro posto il codice ridotto di un esempio del caso.
Ciao