Come non bloccare mai l’interfaccia con le NSOperation

Il problema del caricamento da internet o dell’elaborazione di un grande numero di dati è che l’interfaccia dell’applicazione si blocca facilmente e non risponde ai comandi dell’utente. Questo è dovuto al fatto che i metodi incriminati vengono tutti eseguiti nel thread principale, ovvero quello in cui gira anche il loop dell’interfaccia.

Possibili soluzioni sono quei metodi del tipo performSelectorInBackground:withObject: che però, all’atto pratico non garantiscono una fluidità dell’interfaccia in casi come il caricamento di una immagine da internet tramite il metodo dataWithContentsOfURL:.

Per risolvere definitivamente questo problema è necessario non solo che un metodo potenzialmente bloccante giri su un thread diverso dal MainThread, ma che lo faccia senza fretta e quindi senza pericolo che il sistema venga lo stesso bloccato.

Per questo Apple ha messo a disposizione la classe NSOperationQueue che permette di aggiungere ad una lista d’attesa una serie di metodi da eseguire in background. Appena sarà possibile, l’app le eseguirà una dietro l’altra. Il tempi di attesa possono risultare leggermente superiori, ma ne gioverà l’usabilità dell’interfaccia, con utenti più contenti.

Ecco un esempio del modo più semplice di usare questa classe:

1
2
3
4
5
self.queue = [NSOperationQueue new];
 
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadData) object:nil];
 
[self.queue addOperation:operation];
self.queue = [NSOperationQueue new];

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadData) object:nil];

[self.queue addOperation:operation];

Nell’esempio queue è una property dichiarata nel .h e quindi richiamabile in ogni parte del codice di implementazione.

È importante però ricordarsi che le modifiche all’interfaccia devono girare nel MainThread e che quindi, all’interno di un metodo inserito in una NSOperation bisogna usare il metodo performSelectorOnMainThread:withObject:waitUntilDone: ogni volta che serve di modificare l’interfaccia, impostando waitUntilDone a YES.