Trucos de rendimiento de iOS para que su aplicación se sienta más eficaz

Publicado: 2022-03-10
Resumen rápido ↬ Un buen rendimiento es fundamental para ofrecer una buena experiencia de usuario, y los usuarios de iOS suelen tener grandes expectativas de sus aplicaciones. Una aplicación lenta y que no responde puede hacer que los usuarios dejen de usar su aplicación o, peor aún, dejen una mala calificación.

Aunque el hardware moderno de iOS es lo suficientemente potente como para manejar muchas tareas intensivas y complejas, el dispositivo aún puede parecer que no responde si no tiene cuidado con el rendimiento de su aplicación. En este artículo, veremos cinco trucos de optimización que harán que su aplicación se sienta más receptiva.

1. Retirar la cola de celdas reutilizables

Probablemente haya usado tableView.dequeueReusableCell(withIdentifier:for:) dentro de tableView(_:cellForRowAt:) antes. ¿Alguna vez se preguntó por qué tiene que seguir esta incómoda API, en lugar de simplemente pasar una matriz de celdas? Vamos a repasar el razonamiento de esto.

Digamos que tiene una vista de tabla con mil filas. Sin usar celdas reutilizables, tendríamos que crear una nueva celda para cada fila, así:

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // Create a new cell whenever cellForRowAt is called. let cell = UITableViewCell() cell.textLabel?.text = "Cell \(indexPath.row)" return cell }

Como habrás pensado, esto agregará mil celdas a la memoria del dispositivo a medida que te desplazas hacia abajo. Imagínese lo que sucedería si cada celda contuviera una UIImageView y mucho texto: ¡Cargarlos todos a la vez podría hacer que la aplicación se quedara sin memoria! Aparte de eso, cada celda individual requeriría que se asigne nueva memoria durante el desplazamiento. Si se desplaza rápidamente por una vista de tabla, se asignarán muchos pequeños fragmentos de memoria sobre la marcha, ¡y este proceso hará que la interfaz de usuario sea inestable!

Para resolver esto, Apple nos ha proporcionado el dequeueReusableCell(withIdentifier:for:) . La reutilización de celdas funciona colocando la celda que ya no está visible en la pantalla en una cola, y cuando una nueva celda está a punto de ser visible en la pantalla (por ejemplo, la siguiente celda a continuación a medida que el usuario se desplaza hacia abajo), la vista de tabla recuperar una celda de esta cola y modificarla en el cellForRowAt indexPath:

Mecanismo de cola de reutilización de celdas
Cómo funcionan las colas de reutilización de celdas en iOS (vista previa grande)

Al usar una cola para almacenar celdas, la vista de tabla no necesita crear mil celdas. En cambio, necesita suficientes celdas para cubrir el área de la vista de tabla.

Al usar dequeueReusableCell , podemos reducir la memoria utilizada por la aplicación y hacer que sea menos propenso a quedarse sin memoria.

¡Más después del salto! Continúe leyendo a continuación ↓

2. Usar una pantalla de inicio que se parece a la pantalla inicial

Como se menciona en las Pautas de interfaz humana (HIG) de Apple, las pantallas de inicio se pueden usar para mejorar la percepción de la capacidad de respuesta de una aplicación:

“Su única intención es mejorar la percepción de que su aplicación se inicia rápidamente y está lista para usar de inmediato. Cada aplicación debe proporcionar una pantalla de inicio”.

Es un error común usar una pantalla de inicio como pantalla de inicio para mostrar la marca o para agregar una animación de carga. Diseñe la pantalla de inicio para que sea idéntica a la primera pantalla de su aplicación, como lo menciona Apple:

“Diseñe una pantalla de inicio que sea casi idéntica a la primera pantalla de su aplicación. Si incluye elementos que se ven diferentes cuando la aplicación termina de iniciarse, las personas pueden experimentar un destello desagradable entre la pantalla de inicio y la primera pantalla de la aplicación.

“La pantalla de inicio no es una oportunidad de marca. No diseñe una experiencia de entrada que parezca una pantalla de inicio o una ventana "Acerca de". No incluya logotipos u otros elementos de marca a menos que sean una parte estática de la primera pantalla de su aplicación”.

El uso de una pantalla de inicio para fines de carga o marca podría ralentizar el tiempo del primer uso y hacer que el usuario sienta que la aplicación es lenta.

Cuando inicie un nuevo proyecto de iOS, se creará un LaunchScreen.storyboard en blanco. Esta pantalla se mostrará al usuario mientras la aplicación carga los controladores de vista y el diseño.

Para que su aplicación se sienta más rápida, puede diseñar la pantalla de inicio para que sea similar a la primera pantalla (controlador de vista) que se mostrará al usuario.

Por ejemplo, la pantalla de inicio de la aplicación Safari es similar a su primera vista:

La pantalla de inicio y la primera vista son similares
Una comparación de la pantalla de inicio y la primera vista de la aplicación Safari (vista previa grande)

El guión gráfico de la pantalla de inicio es como cualquier otro archivo de guión gráfico, excepto que solo puede usar las clases estándar de UIKit, como UIViewController, UITabBarController y UINavigationController. Si intenta usar cualquier otra subclase personalizada (como UserViewController), Xcode le notificará que está prohibido usar nombres de clase personalizados.

Xcode muestra un error cuando se usa una clase personalizada
El guión gráfico de la pantalla de inicio no puede contener una clase estándar que no sea UIKit. (Vista previa grande)

Otra cosa a tener en cuenta es que UIActivityIndicatorView no se anima cuando se coloca en la pantalla de inicio, porque iOS generará una imagen estática del guión gráfico de la pantalla de inicio y se la mostrará al usuario. (Esto se menciona brevemente en la presentación de WWDC 2014 "Platforms State of the Union", alrededor de 01:21:56 ).

HIG de Apple también nos aconseja que no incluyamos texto en nuestra pantalla de inicio, porque la pantalla de inicio es estática y no se puede localizar el texto para atender a diferentes idiomas.

Lectura recomendada : Aplicación móvil con función de reconocimiento facial: cómo hacerla realidad

3. Restauración de estado para controladores de vista

La conservación y la restauración del estado permiten al usuario volver exactamente al mismo estado de la interfaz de usuario que tenía justo antes de salir de la aplicación. A veces, debido a la falta de memoria, es posible que el sistema operativo deba eliminar su aplicación de la memoria mientras la aplicación está en segundo plano, y la aplicación puede perder el rastro de su último estado de IU si no se conserva, lo que posiblemente provoque que los usuarios pierdan su trabajo. ¡en curso!

En la pantalla multitarea, podemos ver una lista de aplicaciones que se han puesto en segundo plano. Podríamos suponer que estas aplicaciones aún se ejecutan en segundo plano; en realidad, algunas de estas aplicaciones pueden ser eliminadas y reiniciadas por el sistema debido a las demandas de memoria. Las instantáneas de la aplicación que vemos en la vista multitarea son en realidad capturas de pantalla tomadas por el sistema justo cuando salimos de la aplicación (es decir, para ir a la pantalla de inicio o multitarea).

iOS crea la ilusión de que las aplicaciones se ejecutan en segundo plano tomando una captura de pantalla de la vista más reciente
Capturas de pantalla de aplicaciones tomadas por iOS cuando el usuario sale de la aplicación (vista previa grande)

iOS usa estas capturas de pantalla para dar la ilusión de que la aplicación aún se está ejecutando o aún muestra esta vista en particular, mientras que la aplicación podría haber terminado o reiniciado en segundo plano mientras aún muestra la misma captura de pantalla.

¿Alguna vez ha experimentado, al reanudar una aplicación desde la pantalla multitarea, que la aplicación muestra una interfaz de usuario diferente a la instantánea que se muestra en la vista multitarea? Esto se debe a que la aplicación no implementó el mecanismo de restauración de estado y los datos mostrados se perdieron cuando la aplicación se eliminó en segundo plano. Esto puede conducir a una mala experiencia porque el usuario espera que su aplicación esté en el mismo estado que cuando la dejó.

Del artículo de Apple:

“Esperan que tu aplicación esté en el mismo estado que cuando la dejaron. La conservación y restauración del estado garantiza que su aplicación vuelva a su estado anterior cuando se inicie de nuevo”.

UIKit hace mucho trabajo para simplificarnos la conservación y restauración del estado: Maneja el guardado y la carga del estado de una aplicación automáticamente en los momentos apropiados. Todo lo que tenemos que hacer es agregar alguna configuración para decirle a la aplicación que admita la conservación y restauración del estado y decirle a la aplicación qué datos deben conservarse.

Para habilitar el guardado y la restauración del estado, podemos implementar estos dos métodos en AppDelegate.swift :

 func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool { return true }
 func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool { return true }

Esto le indicará a la aplicación que guarde y restaure el estado de la aplicación automáticamente.

A continuación, le indicaremos a la aplicación qué controladores de vista deben conservarse. Hacemos esto especificando la "ID de restauración" en el guión gráfico:

Configuración de la ID de restauración en el guión gráfico
Configuración de ID de restauración en el guión gráfico (vista previa grande)

También puede marcar "Usar ID de guión gráfico" para usar el ID de guión gráfico como ID de restauración.

Para establecer el ID de restauración en el código, podemos usar la propiedad restorationIdentifier del controlador de vista.

 // ViewController.swift self.restorationIdentifier = "MainVC"

Durante la preservación del estado, cualquier controlador de vista o vista a la que se le haya asignado un identificador de restauración tendrá su estado guardado en el disco.

Los identificadores de restauración se pueden agrupar para formar una ruta de restauración. Los identificadores se agrupan mediante la jerarquía de vista, desde el controlador de vista raíz hasta el controlador de vista activo actual. Supongamos que MyViewController está incrustado en un controlador de navegación, que está incrustado en otro controlador de barra de pestañas. Suponiendo que estén usando sus propios nombres de clase como identificadores de restauración, la ruta de restauración se verá así:

 TabBarController/NavigationController/MyViewController

Cuando el usuario abandona la aplicación con MyViewController como controlador de vista activo, la aplicación guardará esta ruta; luego, la aplicación recordará la jerarquía de vista anterior que se muestra (Controlador de barra de pestañasControlador de navegaciónMi controlador de vista ).

Después de asignar el identificador de restauración, necesitaremos implementar los métodos encodeRestorableState(with coder:) y decodeRestorableState(with coder:) para cada uno de los controladores de vista conservados. Estos dos métodos nos permiten especificar qué datos deben guardarse o cargarse y cómo codificarlos o decodificarlos.

Veamos el controlador de vista:

 // MyViewController.swift​ // MARK: State restoration // UIViewController already conforms to UIStateRestoring protocol by default extension MyViewController { // will be called during state preservation override func encodeRestorableState(with coder: NSCoder) { // encode the data you want to save during state preservation coder.encode(self.username, forKey: "username") super.encodeRestorableState(with: coder) } // will be called during state restoration override func decodeRestorableState(with coder: NSCoder) { // decode the data saved and load it during state restoration if let restoredUsername = coder.decodeObject(forKey: "username") as? String { self.username = restoredUsername } super.decodeRestorableState(with: coder) } }

Recuerde llamar a la implementación de la superclase en la parte inferior de su propio método. Esto garantiza que la clase principal tenga la oportunidad de guardar y restaurar el estado.

Una vez que los objetos hayan terminado de decodificarse, se llamará a applicationFinishedRestoringState() para decirle al controlador de vista que se ha restaurado el estado. Podemos actualizar la interfaz de usuario para el controlador de vista en este método.

 // MyViewController.swift​ // MARK: State restoration // UIViewController already conforms to UIStateRestoring protocol by default extension MyViewController { ... override func applicationFinishedRestoringState() { // update the UI here self.usernameLabel.text = self.username } }

¡Ahí tienes! Estos son los métodos esenciales para implementar la conservación y restauración del estado de su aplicación. Tenga en cuenta que el sistema operativo eliminará el estado guardado cuando el usuario cierre la aplicación a la fuerza, para evitar quedarse atascado en un estado roto en caso de que algo salga mal en la conservación y restauración del estado.

Además, no almacene ningún dato del modelo (es decir, datos que deberían haberse guardado en UserDefaults o Core Data) en el estado, aunque parezca conveniente hacerlo. Los datos de estado se eliminarán cuando el usuario cierre su aplicación y, ciertamente, no desea perder los datos del modelo de esta manera.

Para probar si la conservación y restauración del estado están funcionando bien, siga los pasos a continuación:

  1. Cree y ejecute una aplicación con Xcode.
  2. Navegue a la pantalla con la conservación y restauración del estado que desea probar.
  3. Regrese a la pantalla de inicio (deslizando hacia arriba o haciendo doble clic en el botón de inicio, o presionando Shift ⇧ + Cmd ⌘ + H en el simulador) para enviar la aplicación al fondo.
  4. Detenga la aplicación en Xcode presionando el botón.
  5. Inicie la aplicación nuevamente y verifique si el estado se ha restaurado correctamente.

Debido a que esta sección solo cubre los aspectos básicos de la conservación y restauración del estado, recomiendo los siguientes artículos de Apple Inc. para un conocimiento más profundo de la restauración del estado:

  1. Estado de conservación y restauración
  2. Proceso de conservación de la interfaz de usuario
  3. Proceso de restauración de la interfaz de usuario

4. Reduzca el uso de vistas no opacas tanto como sea posible

Una vista opaca es una vista que no tiene transparencia, lo que significa que cualquier elemento de la interfaz de usuario colocado detrás de ella no es visible en absoluto. Podemos configurar una vista para que sea opaca en Interface Builder:

Esto informará al sistema de dibujo para omitir el dibujo de lo que sea que esté detrás de esta vista.
Establezca UIView en opaco en el guión gráfico (vista previa grande)

O podemos hacerlo programáticamente con la propiedad isOpaque de UIView:

 view.isOpaque = true

Establecer una vista en opaca hará que el sistema de dibujo optimice el rendimiento del dibujo mientras se renderiza la pantalla.

Si una vista tiene transparencia (es decir, alfa está por debajo de 1.0), entonces iOS tendrá que hacer un trabajo adicional para calcular lo que debe mostrarse combinando diferentes capas de vistas en la jerarquía de vistas. Por otro lado, si una vista se configura como opaca, entonces el sistema de dibujo solo colocará esta vista al frente y evitará el trabajo adicional de combinar las múltiples capas de vista detrás de ella.

Puede comprobar qué capas se están fusionando (no opacas) en el simulador de iOS marcando DepurarCapas fusionadas de color .

El verde no está mezclado con color, el rojo es una capa combinada
Mostrar capas de colores combinados en Simulator

Después de marcar la opción Color Blended Layers , puede ver que algunas vistas son rojas y otras verdes. El rojo indica que la vista no es opaca y que su visualización de salida es el resultado de capas combinadas detrás de ella. Verde indica que la vista es opaca y no se ha realizado ninguna combinación.

Con un fondo de color opaco, la capa no necesita mezclarse con otra capa
Asigne un color de fondo no transparente a UILabel siempre que sea posible para reducir las capas de mezcla de colores. (Vista previa grande)

Las etiquetas que se muestran arriba ("Ver amigos", etc.) están resaltadas en rojo porque cuando se arrastra una etiqueta al guión gráfico, su color de fondo se establece en transparente de forma predeterminada. Cuando el sistema de dibujo está componiendo la visualización cerca del área de la etiqueta, solicitará la capa detrás de la etiqueta y realizará algunos cálculos.

Una forma de optimizar el rendimiento de la aplicación es reducir la cantidad de vistas resaltadas en rojo tanto como sea posible.

Al cambiar label.backgroundColor = UIColor.clear a label.backgroundColor = UIColor.white , podemos reducir la combinación de capas entre la etiqueta y la capa de vista detrás de ella.

El uso de un color de fondo transparente hará que la capa se mezcle
Muchas etiquetas están resaltadas en rojo porque su color de fondo es transparente, lo que hace que iOS calcule el color de fondo al combinar la vista detrás de él. (Vista previa grande)

Es posible que haya notado que, incluso si ha configurado un UIImageView como opaco y le ha asignado un color de fondo, el simulador seguirá mostrando rojo en la vista de la imagen. Esto probablemente se deba a que la imagen que usó para la vista de imagen tiene un canal alfa.

Para eliminar el canal alfa de una imagen, puede usar la aplicación Vista previa para hacer un duplicado de la imagen ( Shift ⇧ + Cmd ⌘ + S ), y desmarque la casilla de verificación "Alfa" al guardar.

Desmarque la casilla de verificación 'Alfa' al guardar una imagen para descartar el canal alfa.
Desactive la casilla de verificación 'Alfa' al guardar una imagen para descartar el canal alfa. (Vista previa grande)

5. Pase funciones de procesamiento pesado a subprocesos de fondo (GCD)

Debido a que UIKit solo funciona en el subproceso principal, realizar un procesamiento pesado en el subproceso principal ralentizará la interfaz de usuario. UIKit utiliza el subproceso principal no solo para manejar y responder a la entrada del usuario, sino también para dibujar la pantalla.

La clave para hacer que una aplicación responda es mover tantas tareas pesadas de procesamiento a subprocesos en segundo plano como sea posible. Evite realizar cálculos complejos, redes y operaciones de E/S pesadas (por ejemplo, leer y escribir en el disco) en el subproceso principal.

Es posible que alguna vez haya usado una aplicación que de repente dejó de responder a su entrada táctil y parece que la aplicación se ha colgado. Lo más probable es que esto se deba a que la aplicación ejecuta tareas de cálculo pesadas en el subproceso principal.

El subproceso principal generalmente alterna entre tareas de UIKit (como el manejo de la entrada del usuario) y algunas tareas livianas en pequeños intervalos. Si se está ejecutando una tarea pesada en el subproceso principal, entonces UIKit deberá esperar hasta que la tarea pesada haya terminado antes de poder manejar la entrada táctil.

Evite ejecutar tareas que consumen mucho tiempo o mucho rendimiento en el subproceso principal
Así es como el subproceso principal maneja las tareas de la interfaz de usuario y por qué hace que la interfaz de usuario se bloquee cuando se realizan tareas pesadas. (Vista previa grande)

De forma predeterminada, el código dentro de los métodos de ciclo de vida del controlador de vista (como viewDidLoad) y las funciones IBOutlet se ejecutan en el subproceso principal. Para mover tareas pesadas de procesamiento a un subproceso en segundo plano, podemos usar las colas Grand Central Dispatch proporcionadas por Apple.

Aquí está la plantilla para cambiar colas:

 // Switch to background thread to perform heavy task. DispatchQueue.global(qos: .default).async { // Perform heavy task here. // Switch back to main thread to perform UI-related task. DispatchQueue.main.async { // Update UI. } }

El qos significa "calidad de servicio". Diferentes valores de calidad de servicio indican diferentes prioridades para las tareas especificadas. El sistema operativo asignará más tiempo de CPU y rendimiento de E/S de potencia de CPU para tareas asignadas en colas con valores de QoS más altos, lo que significa que una tarea terminará más rápido en una cola con valores de QoS más altos. Un valor de QoS más alto también consumirá más energía debido a que utiliza más recursos.

Aquí está la lista de valores de QoS de mayor a menor prioridad:

Valores de calidad de servicio de la cola ordenados por rendimiento y eficiencia energética
Valores de calidad de servicio de la cola ordenados por rendimiento y eficiencia energética (vista previa grande)

Apple ha proporcionado una tabla útil con ejemplos de qué valores de QoS usar para diferentes tareas.

Una cosa a tener en cuenta es que todo el código UIKit siempre debe ejecutarse en el subproceso principal. La modificación de objetos UIKit (como UILabel y UIImageView ) en el subproceso de fondo podría tener una consecuencia no deseada, como que la interfaz de usuario no se actualice realmente, se produzca un bloqueo, etc.

Del artículo de Apple:

"Actualizar la interfaz de usuario en un subproceso que no sea el principal es un error común que puede dar lugar a actualizaciones de la interfaz de usuario perdidas, defectos visuales, corrupción de datos y bloqueos".

Recomiendo ver el video de la WWDC 2012 de Apple sobre la simultaneidad de la interfaz de usuario para comprender mejor cómo crear una aplicación receptiva.

notas

La compensación de la optimización del rendimiento es que debe escribir más código o configurar ajustes adicionales además de la funcionalidad de la aplicación. Esto podría hacer que su aplicación se entregue más tarde de lo esperado, y tendrá más código para mantener en el futuro, y más código significa potencialmente más errores.

Antes de dedicar tiempo a optimizar su aplicación, pregúntese si la aplicación ya es fluida o si tiene alguna parte que no responde y que realmente necesita optimizarse. Es posible que no valga la pena dedicar mucho tiempo a optimizar una aplicación que ya funciona para reducir 0,01 segundos, ya que se podría emplear mejor el tiempo desarrollando mejores funciones u otras prioridades.

Más recursos

  • “Una suite de deliciosos dulces para la vista de iOS”, Tim Oliver, Tokyo iOS Meetup 2018 (Video)
  • “Creación de interfaces de usuario simultáneas en iOS”, Andy Matuschak, WWDC 2012 (video)
  • "Preservar la interfaz de usuario de su aplicación en todos los lanzamientos", Apple
  • “Guía de programación de simultaneidad: Colas de despacho”, Archivo de documentación, Apple
  • “Comprobador de hilos principales”, Apple