Instruments

Optimizando el rendimiento de UITableView

Una de las cosas más molestas que te puedes encontrar cuando estás utilizando una app para iOS es que las partes en las que hay algún UITableView implicado no vayan todo lo finas que debieran cuando haces scroll. Esta tarea, aunque parezca trivial, tiene su miga. La tasa de frames mínima para que consideremos un scrolling como suave es de 30fps. Esto implica que disponemos de unos 33ms para renderizar cada frame. Dicho así no suena tan trivial ¿no?

1 segundo = 1000ms
1000/30 ≂ 33.3ms

Si nos ponemos gallos y subimos la exigencia para una experiencia deliciosamete suave, exigiremos que el sistema sea capaz de gestionar los gráficos a 60fps. Eso implica que solo dispondremos de unos 16-17ms para renderizar cada frame.

Teniendo estos datos en cuenta, en este post pretendo ir desgranando varias técnicas para mejorar el rendimiento gráfico de UITableView hasta llegar a la que, hasta donde yo sé, ofrece los mejores resultados. Se trata de la técnica que en su día utilizó Loren Brichter para que su aplicación, Tweetie —posteriormente comprada por Twitter—, mostrase un scroll del timeline suave como la seda incluso en el iPhone 3G. Esta es la solución más extrema. Aunque el mecanismo utilizado es simple a nivel conceptual, requiere dibujar el contenido de las UITableViewCell-s desde código fuente. No es que esto sea complicado —al menos para celdas de una complejidad normal— pero verás que con las otras técnicas que explico, también se mejora bastante el rendimiento y el proceso es algo más… directo.

Para terminar, explicaré la forma de optimizar el rendimiento de un UITableView cuyas filas contienen imágenes obtenidas de un servicio web —Flickr en nuestro caso—, descargándolas y descomprimiéndolas en segundo plano.

Para empezar, si quieres seguir las explicaciones de forma adecuada, te recomiendo que te bajes el proyecto de ejemplo desde Github.

Midiendo el rendimiento con Instruments

Vamos a empezar por el modelo de UITableViewCell menos optimizado. Para ello cargaremos un UITableView de mil filas utilizando celdas instanciadas desde un XIB. Las celdas tendrán una estructura básica, pero suficiente para ir viendo como mejorar el rendimiento del scrolling. Si abres el proyecto de ejemplo, el modelo de celda que vamos a utilizar está en /Optimizacion UITableView/Optimizacion UITableView/Views/SPOCeldaNoOptimizadaCell.xib.

Como puedes ver, la celda consta de un UIImageView con un PNG con transparencia, un UILabel para el título y otro UILabel para la descripción o subtítulo.

Una de las cosas que más daño hace al rendimiento gráfico de nuestros dispositivos iOS es la utilización de capas con transparencias. Este hecho obliga al sistema gráfico a utilizar mezclas de capaslayer blending—, lo cual reduce claramente el rendimiento del dispositivo.

Inicia la aplicación de muestra y selecciona la opción Celdas desde XIB del menú inicial. Puedes ver qué elementos de nuestra interfaz utilizan capas transparentes y, por tanto, hacen uso de layer blending accediendo al menú Depurar -> Resaltar capas mezcladas del simulador de iOS. Verás algo similar a esto:

Celdas desde XIB no optimizadas

Las zonas verdes son zonas opacas —bien— mientras que las rojas son zonas con transparencia —mal. Vamos a ir un poco más allá y ver cuál es el rendimiento real de este UITableView en un iPhone midiéndolo con Instruments. Para ello, obviamente, te hace falta un iPhone con un provisioning profile válido instalado. Conectas el iPhone, eliges el dispositivo en la barra de herramientas y mantienes pulsado el botón izquierdo del ratón sobre el botón Play hasta que salga un menú en el que elegirás Profile.

Profile app

Cuando arranque la aplicación, tendremos que elegir la plantilla Core Animation de la sección Graphics de Instruments. Una vez hecho esto empezaremos a muestrear nuestra app directamente desde el iPhone y a ver en tiempo real la medición de FPSs a la que se mueve nuestra interfaz. También podemos ver directamente en el iPhone qué elementos de nuestra interfaz utilizan capas transparentes y, por tanto, hacen uso de layer blending marcando la opción Color blend layers del panel de la izquierda.

Observando el rendimiento en Instruments

Con los iPhones más modernos —iPhone 5 y 4S— se obtiene una tasa de frames bastante alta —por encima de los 45 y llegando a los 60 por momentos— pero esto no es así con iPhones más ancianos en los que la tasa de frames se resiente bastante más y desde luego, no va a ser así con celdas más complejas.

Optimizando las celdas del XIB

La forma más fácil de optimizar la celda que obtenemos del XIB, es haciendo que los UILabel-s sean opacos y poniéndoles un color de fondo de forma explícita. Para ello, en Interface Builder, seleccionamos los UILabels-s que nos interesan y en el Attributes Inspector, en la sección View marcamos la opción Opaque e indicamos un Background de color blanco.

Propiedad opaque de UIView

Vamos a ver qué efecto tiene esto que acabamos de hacer. Arranca la aplicación de muestra y selecciona la opción Celdas desde XIB optimizadas del menú inicial. Si, como hemos hecho anteriormente, activas la opción de resaltar las capas mezcladas verás que esta vez hay mucho menos rojo y bastante más verde.

Layer blending con labels opacos

El rendimiento el iPhones más antiguos va a mejorar bastante con tan solo esta modificación.

Método Tweetie, para hombres de pelo en pecho

El método utilizado por Loren Brichter basa su eficacia en evitar a toda costa el uso de subvistas dibujando todo el contenido de la celda en un solo contenedor mediante Core Graphics.

Con esta premisa tan simple, la app Tweetie consiguió que se pudiese hacer scroll en el timeline de Twitter de forma suave incluso en el iPhone 3G. Además, Loren Brichter tuvo el detalle de ofrecer de forma libre su implementación base de UITableViewCell. Esta implementación se puede encontrar en Github.

Yo he modificado ligeramente dicha implementación para agregarle soporte ARC y la tienes disponible en el proyecto de muestra de este artículo en la ruta /Optimizacion UITableView/Optimizacion UITableView/Views/ o directamente en Github.

Como decía anteriormente, Loren Brichter ofreció la implementación base de su particular UITableViewCell. Nuestra tarea será crear una subclase de ABTableViewCell e implementar el método drawContentView:highlighted:. Puedes ver el resultado en el archivo /Optimizacion UITableView/Optimizacion UITableView/Views/SPOTableViewCell.m o directamente en Github. Unas cuantas anotaciones sobre dicha implementación:

  • Todas las instancias de UIFont y UIColor las creamos en el método de clase initialize de forma que estas solo se generen la primera vez que la clase SPOTablewViewCell recibe un mensaje evitando así instanciaciones extra innecesarias cada vez que se genera una nueva celda.
  • En el método drawContentView:highlighted: hacemos uso de las funciones de Core Graphics para dibujar la celda con su correspondiente imagen, título y subtítulo.

Si ejecutas la aplicación de muestra y activas la opción de resaltar las capas mezcladas verás que esta vez las celdas son completamente verdes y además, cada celda está compuesta de una sola vista, provocando que el rendimiento del sistema gráfico sea óptimo.

Celdas tipo Tweetie

Descarga y descompresión asíncrona de thumbnails

Es muy probable que, en un momento dado, tengas que implementar un UITableView en el que se muestren imágenes que debas obtener de algún servicio web. En este proceso hay dos puntos en los que el rendimiento de la tabla se puede ver afectado, más allá de los que hemos descrito anteriormente:

  1. La descarga de la imagen: obviamente, si esperamos a que la imagen se descargue antes de mostrar la celda el rendimiento obtenido va a ser pésimo. Esto es fácilmente solucionable con NSURLConnection o, mejor aún, con uno de los múltiples frameworks para networking que podemos encontrar hoy en día. Mis preferidos: AFNetworking y MKNetworkKit.
  2. La descompresión de la imagen: este punto es quizá el que más desapercibido pasa y que puede suponer la diferencia entre un rendimiento bueno y uno sublime. Recuerda que un archivo JPG o PNG es un archivo de imagen comprimido y que para poder utilizarlo debemos descomprimirlo primero. UIKit realiza este proceso por su cuenta la primera vez que mostramos la imagen y además se encarga de cachearla. El problema es que UIKit hace todo esto en el hilo principal o main thread. Como sabrás, este es el mismo hilo en que se realizan todas las tareas relacionadas con la interfaz de usuario. Por tanto, cuando trabajamos con gran cantidad de imágenes —o con imágenes de gran tamaño—, la descompresión se puede convertir en un problema.

    Si quieres ver un estudio muy gráfico del impacto que tiene la descompresión de una imagen en el tiempo que lleva procesarla para mostrarla en pantalla, no te pierdas este informe de Cocoanetics. Y ahora recuerda que lo he escrito al principio de este artículo. Si queremos una suavidad mínimamente aceptable, necesitamos renderizar cada frame en unos 33ms. Con el tiempo que lleva descomprimir cada imagen, alcanzar ese ritmo de forma continua es imposible.

Por suerte, MKNetworkKit —al parecer AFNetworking aún no descomprime imágenes en segundo plano— nos facilita bastante esta tarea ya que dispone de una categoría de UIImageView que añade varios métodos muy oportunos para solucionar este inconveniente:

// …
NSString *urlImagen = @"http://url.de.la.imagen";
[self.fotoImageView setImageFromURL:[NSURL URLWithString:urlImagen] placeholderImage:nil];

Ten en cuenta que self.fotoImageView es un UIImageView. El método setImageFromURL:placeholderImage: es un método de categoría añadido por MKNetworkKit que se encargará de descargar la imagen y descomprimirla en 2º plano. Si quieres ver esto en funcionamiento, echa un ojo a /Optimizacion UITableView/Optimizacion UITableView/Views/SPOFlickrImageCell.m o directamente en Github.

Si arrancas la aplicación de muestra y eliges la opción Imágenes desde Flickr, observarás que cuando haces scrolling, las imágenes van apareciendo a medida que se van descargando/descomprimiendo en segundo plano, evitando que las celdas tengan que esperar a que dichos procesos terminen antes de poder ser mostradas.

Descomprimiendo imágenes por nuestra cuenta

Si te interesa realizar la descompresión de imágenes por tu cuenta —por ejemplo para mejorar aún más el método Tweetie— sin depender de librerías de terceros— puedes usar esta implementación.

sendoa

Me llamo Sendoa Portuondo, vivo en Derio, Bizkaia y soy programador de aplicaciones web —sobre todo PHP— y de dispositivos de Apple. Socio fundador de Qbikode Solutions.

Otros Posts - Sitio Web

Seguir:
Twitter

4 pensamientos en “Optimizando el rendimiento de UITableView

  1. Pingback: Giuseppe Basile | WP Stacker link collection: January with 111 links

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

Puedes usar las siguientes etiquetas y atributos HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>