Revista Informática

iOS desde Cero: NSOperations y NSOperationQueues

Publicado el 11 octubre 2013 por Codehero @codeheroblog

NSOperations y NSOperationQueues

NSOperation y NSOperationQueue son clases de Objective-C (operativas a partir de 10.6 y iOS 4) que básicamente gestionan el manejo de procesos simultáneos (hilos). Estas clases almacenan en una cola los procesos a ejecutar hasta que se cancele explícitamente o se termine la ejecución de cada uno de los procesos.

Es posible que después de esta definición, y si han tenido algún tipo de contacto con el lenguaje, hayan escuchado hablar de Grand Central Dispatch (GCD), que en pocas palabras también nos ayuda a manejar múltiples operaciones en una especie de simultaneidad pero a diferencia de nuestras clases ésta es de muy bajo nivel lo que nos hace difícil la manipulación de nuestros procesos. Apple por otro lado nos recomienda siempre utilizar la abstracción de más alto nivel y de ahí ir descendiendo siempre y cuando las mediciones muestren que lo necesitamos.


Demostración

Para demostrar el uso de estas clases, entender mejor cómo funciona y lo importante que son para mejorar el rendimiento de la aplicación, desarrollaremos un ejemplo desde cero:

El ejemplo consta de un UITableView sencillo (ya aprendido en Capítulos anteriores) que carga y procesa una lista de urls donde están almacenadas una serie de imágenes. Para desarrollar esto, lo primero que debemos hacer es crear nuestro TableView en el StoryBoard dando como resultado algo parecido a éste:

Como se observa en la imagen el ViewController consta de un UItableView que contiene una celda (Recuerden agregar el identificador de la celda) a la que agregaremos un UIImageView donde posteriormente cargaremos nuestra imagen, un UILabel en el que identificaremos la posición de la celda.

Luego creamos una clase que hereda de UITableViewCell, para nuestro caso llamaremos RSCell y en la cual declaramos los componente ya graficados en la celda del UItableView en el StoryBoard de la siguiente manera:

@property (nonatomic,weak) IBOutlet UIImageView *imageCell; @property (nonatomic,weak) IBOutlet UILabel *labelDescription;

12 @property(nonatomic,weak)IBOutlet UIImageView *imageCell;@property(nonatomic,weak)IBOutlet UILabel *labelDescription;

Una vez creada la clase de la celda y hechas las asociaciones correspondientes a los componentes del StoryBoard vamos de una vez a armar nuestro DataSource con los requisitos mínimos para su funcionamiento, y de la forma más básica sin aún hacer uso de nuestra clase NSOperations y NSOperationQueues, obteniendo como resultado la siguiente implementación del DataSource:

#pragma mark dataSource -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return _datasource.count; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSString *identificador =@"myCell"; RSCell *cell = [tableView dequeueReusableCellWithIdentifier:identificador]; if (!cell) { cell = [[RSCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identificador]; } cell.labelDescription.text = [NSString stringWithFormat:@"position %i",indexPath.row]; NSData * imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: [_datasource objectAtIndex:indexPath.row]]]; UIImage *image = [UIImage imageWithData:imageData]; [cell.imageCell setImage:image]; return cell; }

1234567891011121314151617181920212223 #pragma mark dataSource -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{  return_datasourcecount;}-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{  NSString *identificador=@"myCell";  RSCell *cell=[tableView dequeueReusableCellWithIdentifier:identificador];    if(!cell)  {  cell=[[RSCell alloc]initWithStyle:UITableViewCellStyleDefault  reuseIdentifier:identificador];  }  celllabelDescriptiontext=[NSString stringWithFormat:@"position %i",indexPathrow];    NSData *imageData=[[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:[_datasource objectAtIndex:indexPathrow]]];   UIImage *image=[UIImage imageWithData:imageData];  [cellimageCell setImage:image];  returncell;}

Si están siguiendo los pasos de manera ordenada ya pueden correr la aplicación y darse cuenta que el performance de la misma es horrible, es prácticamente imposible mover el UITableview para ver el resto de las celdas, debido a que se están cargando las imágenes cada vez que se aproxima una celda al rango de visibilidad.

¿Cómo mejorar el performance de nuestra Aplicación?

La respuesta a esta pregunta es hacer uso del tema que estamos desarrollando en este curso, que básicamente libera al proceso principal de la carga de la imagen agregándola a un proceso secundario en paralelo y haciendo posible que la aplicación continúe mientras cargamos nuestras imágenes. Por otro lado, al agregar la funcionalidad de la carga a otro proceso en paralelo no podremos modificar la interfaz que es manejada por el proceso principal, así que, una vez finalizada la carga debemos retornarla al proceso principal para hacer modificaciones en la interfaz gráfica.

Un proceso paralelo al principal NO puede interactuar con la interfaz de usuario hasta retornarlo al mainQueue

Veamos cómo modificando el método tableView:cellForRowAtIndexPath: para hacer uso del NSOperationQueues y hacer la aplicación más dinámica.

implementaremos nuestra clase de la siguiente forma:

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSString *identificador =@"myCell"; RSCell *cell = [tableView dequeueReusableCellWithIdentifier:identificador]; if (!cell) { cell = [[RSCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identificador]; } [cell.imageCell setImage:nil]; cell.labelDescription.text = [NSString stringWithFormat:@"position %i",indexPath.row]; // Creamos nuesta Queue NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue setName:@"Carga de imagenes"]; // Agregamos bloques de operaciones a nuestro Queue [queue addOperationWithBlock:^{ NSData * imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: [_datasource objectAtIndex:indexPath.row]]]; UIImage *image = [UIImage imageWithData:imageData]; // Retornamos al mainQueue la imagen para ser agregada a la celda [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [cell.imageCell setImage:image]; }]; }]; return cell; }

12345678910111213141516171819202122232425262728293031323334 -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{  NSString *identificador=@"myCell";  RSCell *cell=[tableView dequeueReusableCellWithIdentifier:identificador];    if(!cell)  {  cell=[[RSCell alloc]initWithStyle:UITableViewCellStyleDefault   reuseIdentifier:identificador];  }  [cellimageCell setImage:nil];  celllabelDescriptiontext=[NSString stringWithFormat:@"position %i",indexPathrow];    // Creamos nuesta Queue  NSOperationQueue *queue=[[NSOperationQueue alloc]init];  [queue setName:@"Carga de imagenes"];    // Agregamos bloques de operaciones a nuestro Queue  [queue addOperationWithBlock:^{  NSData *imageData=[[NSData alloc]initWithContentsOfURL:  [NSURL URLWithString:[_datasource objectAtIndex:indexPathrow]]];  UIImage *image=[UIImage imageWithData:imageData];    // Retornamos al mainQueue la imagen para ser agregada a la celda  [[NSOperationQueue mainQueue]addOperationWithBlock:^{  [cellimageCell setImage:image];    }];    }];      returncell;}

Finalmente ya podemos correr nuestra aplicación y darnos cuenta del gran cambio que hacen estas clases a una aplicación para mejorar el performance y la experiencia del usuario. Si todo salió bien pudiéramos tener una aplicación como esta:


Conclusión

En este capítulo aprendimos los conceptos básicos para implementar un NSOperations y NSOperationQueues en nuestra aplicación y así lograr que ésta pueda realizar múltiples tareas sin afectar el flujo original de la aplicación móvil.

Para ampliar tus conocimientos de estas clases te invito a revisar todos los métodos de NSOperations y NSOperationQueues en la documentación oficial de Apple.

Una vez más te recomiendo echarle un vistazo a la serie completa de iOS desde cero, así como a las otras series de CodeHero, agradeciendo de antemano todas sus dudas y comentarios en la sección de comentarios.

¡Hasta el próximo capítulo!


Volver a la Portada de Logo Paperblog