dibujarRect o no a drawRect (¿cuándo debería uno usar DrawRect / Core Graphics versus subvistas / imágenes y por qué?)

Para aclarar el propósito de esta pregunta: sé CÓMO crear vistas complicadas con ambas subvistas y usando drawRect. Intento comprender completamente cuándo y por qué usar uno sobre el otro.

También entiendo que no tiene sentido optimizar mucho antes de tiempo, y hacer algo de la manera más difícil antes de hacer cualquier perfil. Considere que me siento cómodo con ambos métodos, y ahora realmente quiero una comprensión más profunda.

Gran parte de mi confusión proviene de aprender cómo hacer que el rendimiento de desplazamiento de la vista de tabla sea muy sencillo y rápido. Por supuesto, la fuente original de este método es del autor detrás de Twitter para iPhone (anteriormente tweetie). Básicamente, dice que para hacer que la tabla se mueva suavemente, el secreto es NO usar subvistas, sino hacer todo el dibujo en una sola vista personalizada. Básicamente, parece que usar muchas subvistas reduce la velocidad de renderización porque tienen muchos gastos generales y se vuelven a componer constantemente sobre sus vistas principales.

Para ser justos, esto fue escrito cuando el 3GS era bastante nuevo, y los iDevices se han vuelto mucho más rápidos desde entonces. Todavía este método se sugiere regularmente en las redes internas y en otros lugares para tablas de alto rendimiento. De hecho, es un método sugerido en el Código de muestra de Apple , ha sido sugerido en varios videos de WWDC ( Dibujo práctico para desarrolladores de iOS ) y en muchos libros de progtwigción de iOS.

Incluso hay herramientas asombrosas para diseñar gráficos y generar códigos Core Graphics para ellos.

Así que al principio me hacen creer que “existe una razón por la cual Core Graphics existe. ¡Es RÁPIDO!”

Pero tan pronto como creo que tengo la idea de “Favorecer los gráficos centrales cuando sea posible”, empiezo a ver que drawRect a menudo es responsable de la poca capacidad de respuesta en una aplicación, es extremadamente costoso en cuanto a memoria y realmente grava a la CPU. Básicamente, que debería ” evitar anular drawRect ” (WWDC 2012 iOS App Performance: gráficos y animaciones )

Así que supongo que, como todo, es complicado. ¿Tal vez puede ayudarme a mí mismo y a otros a entender cuándo y por qué usar drawRect?

Veo un par de situaciones obvias para usar Core Graphics:

  1. Usted tiene datos dynamics (ejemplo de la Carta de Acciones de Apple)
  2. Tiene un elemento de IU flexible que no se puede ejecutar con una imagen de tamaño variable simple
  3. Está creando un gráfico dynamic, que una vez renderizado se usa en varios lugares

Veo situaciones para evitar Core Graphics:

  1. Las propiedades de su vista deben ser animadas por separado
  2. Usted tiene una jerarquía de vistas relativamente pequeña, por lo que cualquier esfuerzo extra que se perciba utilizando CG no vale la pena.
  3. Desea actualizar partes de la vista sin volver a dibujar todo
  4. El diseño de sus subvistas debe actualizarse cuando cambie el tamaño de vista principal

Así que otorga tu conocimiento. ¿En qué situaciones llega a drawRect / Core Graphics (que también se puede lograr con subvistas)? ¿Qué factores te llevan a esa decisión? ¿Cómo / Por qué se está recomendando el dibujo en una vista personalizada para el desplazamiento mantecoso de las celdas de la tabla sin problemas, pero Apple aconseja que se rechace drawRect por motivos de rendimiento en general? ¿Qué pasa con las imágenes de fondo simples (cuando las creas con CG vs usando una imagen png de tamaño variable)?

Puede que no se necesite una comprensión profunda de este tema para hacer aplicaciones que valgan la pena, pero no me gusta elegir entre técnicas sin poder explicar por qué. Mi cerebro se enoja conmigo.

Actualización de pregunta

Gracias a todos por la información. Algunas preguntas aclaratorias aquí:

  1. Si está dibujando algo con gráficos centrales, pero puede lograr lo mismo con UIImageViews y un png renderizado previamente, ¿debería ir siempre por esa ruta?
  2. Una pregunta similar: especialmente con herramientas rudas como esta , ¿cuándo debería considerar dibujar elementos de interfaz en gráficos centrales? (Probablemente cuando la pantalla de su elemento es variable, por ejemplo, un botón con 20 variaciones de color diferentes. ¿Algún otro caso?)
  3. Dado mi entendimiento en mi respuesta a continuación, ¿podrían obtenerse las mismas ganancias de rendimiento para una celda de la tabla al capturar efectivamente un bitmap instantáneo de su celda después de su renderizado UIView complejo, y mostrar eso mientras se desplaza y oculta su vista compleja? Obviamente, algunas piezas tendrían que resolverse. Solo una idea interesante que tuve.

Quédese con UIKit y las subvistas siempre que pueda. Puede ser más productivo y aprovechar todos los mecanismos OO que deberían ser más fáciles de mantener. Usa Core Graphics cuando no puedas obtener el rendimiento que necesitas de UIKit, o ya sabes que tratar de hackear juntos los efectos de dibujo en UIKit sería más complicado.

El flujo de trabajo general debe ser construir las vistas de tabla con las subvistas. Use instrumentos para medir la velocidad de cuadros en el hardware más antiguo que admita su aplicación. Si no puede obtener 60 fps, desplácese a CoreGraphics. Cuando has hecho esto por un tiempo, tienes una idea de cuando UIKit es probablemente una pérdida de tiempo.

Entonces, ¿por qué Core Graphics es rápido?

CoreGraphics no es realmente rápido. Si se usa todo el tiempo, probablemente vas lento. Es una API de dibujo rica, que requiere que su trabajo se realice en la CPU, a diferencia de una gran cantidad de trabajo de UIKit que se descarga a la GPU. Si tuviera que animar una bola moviéndose por la pantalla, sería una idea terrible llamar a setNeedsDisplay en una vista 60 veces por segundo. Entonces, si tiene subcomponentes de su vista que necesitan ser animados individualmente, cada componente debe ser una capa separada.

El otro problema es que cuando no se hace un dibujo personalizado con drawRect, UIKit puede optimizar las vistas de stock, por lo que drawRect no funciona, o puede tomar atajos con la composición. Cuando anula drawRect, UIKit debe tomar la ruta lenta porque no tiene idea de lo que está haciendo.

Estos dos problemas pueden ser compensados ​​por los beneficios en el caso de las celdas de vista de tabla. Después de llamar a drawRect cuando una vista aparece por primera vez en la pantalla, los contenidos se almacenan en caché, y el desplazamiento es una traducción simple realizada por la GPU. Debido a que se trata de una única vista, en lugar de una jerarquía compleja, las optimizaciones de DrawIct de UIKit pierden importancia. Entonces, el cuello de botella se convierte en cuánto puede optimizar su dibujo de Core Graphics.

Siempre que puedas, usa UIKit. Haga la implementación más simple que funciona. Perfil. Cuando hay un incentivo, optimiza.

La diferencia es que UIView y CALayer esencialmente se ocupan de imágenes fijas. Estas imágenes se cargan en la tarjeta gráfica (si conoce OpenGL, piense en una imagen como una textura y en UIView / CALayer como un polígono que muestra esa textura). Una vez que una imagen está en la GPU, se puede dibujar muy rápidamente, e incluso varias veces, y (con una leve penalización de rendimiento) incluso con niveles variables de transparencia alfa encima de otras imágenes.

CoreGraphics / Quartz es una API para generar imágenes. Toma un búfer de píxeles (de nuevo, piensa en la textura OpenGL) y cambia los píxeles individuales dentro de él. Todo esto sucede en la memoria RAM y en la CPU, y solo una vez que Quartz finaliza, la imagen se “vacia” de nuevo a la GPU. Este viaje de ida y vuelta para obtener una imagen de la GPU, cambiarla y luego cargar toda la imagen (o al menos una parte comparativamente grande) de vuelta a la GPU es bastante lenta. Además, el dibujo real que hace Quartz, aunque realmente rápido para lo que estás haciendo, es mucho más lento que lo que hace la GPU.

Eso es obvio, teniendo en cuenta que la GPU se mueve principalmente en píxeles sin cambiar en grandes fragmentos. Quartz tiene acceso aleatorio de píxeles y comparte la CPU con redes, audio, etc. Además, si tiene varios elementos que dibuja usando Quartz al mismo tiempo, debe volver a dibujarlos cuando cambie uno, luego cargue el todo, mientras que si cambias una imagen y luego dejas que UIViews o CALayers peguen en tus otras imágenes, puedes salir cargando cantidades mucho menores de datos a la GPU.

Cuando no implementa -drawRect :, la mayoría de las vistas solo pueden optimizarse. No contienen ningún píxel, por lo que no pueden dibujar nada. Otras vistas, como UIImageView, solo dibujan un UIImage (que, una vez más, es esencialmente una referencia a una textura, que probablemente ya se haya cargado en la GPU). Entonces, si dibujas el mismo UIImage 5 veces con UIImageView, solo se carga en la GPU una vez y luego se dibuja en la pantalla en 5 ubicaciones diferentes, lo que nos ahorra tiempo y CPU.

Cuando implementa -drawRect :, esto hace que se cree una nueva imagen. Luego recurres a eso en la CPU usando Quartz. Si dibuja un UIImage en su drawRect, es probable que descargue la imagen de la GPU, la copie en la imagen que está dibujando y, una vez que haya terminado, carga esta segunda copia de la imagen a la tarjeta gráfica. Por lo tanto, está usando dos veces la memoria de la GPU en el dispositivo.

Por lo tanto, la manera más rápida de dibujar es generalmente mantener el contenido estático separado de los cambios de contenido (en UIViews / UIView subclases / CALayers). Cargue contenido estático como un UIImage y dibuje usando UIImageView y coloque contenido generado dinámicamente en tiempo de ejecución en drawRect. Si tiene contenido que se dibuja repetidamente, pero que por sí solo no cambia (es decir, 3 íconos que se muestran en la misma ranura para indicar algún estado) también use UIImageView.

Una advertencia: existe demasiada UIViews. Las áreas particularmente transparentes tienen un costo mayor en la GPU para dibujar, ya que deben mezclarse con otros píxeles detrás de ellas cuando se muestran. Esta es la razón por la que puede marcar un UIView como “opaco”, para indicarle a la GPU que puede borrar todo lo que está detrás de esa imagen.

Si tiene contenido que se genera dinámicamente en el tiempo de ejecución, pero permanece igual durante la duración de la aplicación (por ejemplo, una etiqueta que contiene el nombre de usuario), puede tener sentido dibujar todo una vez usando Quartz, con el texto, el borde del botón, etc., como parte del fondo. Pero eso generalmente es una optimización que no es necesaria a menos que la aplicación Instruments lo indique de manera diferente.

Voy a tratar de mantener un resumen de lo que estoy extrapolando de las respuestas de otros aquí, y hacer preguntas aclaratorias en una actualización de la pregunta original. Pero animo a los demás a que sigan respondiendo y voten por quienes proporcionaron buena información.

Enfoque general

Está bastante claro que el enfoque general, como mencionó Ben Sandofsky en su respuesta , debería ser “Siempre que pueda, use UIKit. Haga la implementación más simple que funcione. Perfil. Cuando hay un incentivo, optimícelo”.

El porque

  1. Hay dos principales cuellos de botella posibles en un iDevice, la CPU y la GPU
  2. La CPU es responsable del dibujo / representación inicial de una vista
  3. GPU es responsable de la mayoría de la animación (Core Animation), efectos de capa, composición, etc.
  4. UIView tiene muchas optimizaciones, almacenamiento en caché, etc., integradas para manejar jerarquías de vistas complejas
  5. Al reemplazar drawRect, se pierde una gran cantidad de beneficios que ofrece UIView, y generalmente es más lento que dejar que UIView maneje el renderizado.

Dibujar contenidos de celdas en un plano UIView puede mejorar mucho su FPS en tablas de desplazamiento.

Como dije antes, CPU y GPU son dos posibles cuellos de botella. Como generalmente manejan cosas diferentes, debes prestar atención a qué cuello de botella te enfrentas. En el caso de las tablas de desplazamiento, no es que Core Graphics esté dibujando más rápido, y es por eso que puede mejorar mucho su FPS.

De hecho, Core Graphics puede muy bien ser más lento que una jerarquía anidada de UIView para el render inicial. Sin embargo, parece que la razón típica para el desplazamiento discontinuo es que estás embotellando la GPU, por lo que debes abordar eso.

¿Por qué anular DrawRect (utilizando gráficos básicos) puede ayudar a desplazarse por la tabla?

Por lo que entiendo, la GPU no se responsabiliza por la representación inicial de las vistas, sino que le entrega texturas o mapas de bits, a veces con algunas propiedades de capa, una vez que se han renderizado. A continuación, se encarga de componer los mapas de bits, haciendo que todos los efectos de la capa y la mayoría de la animación (animación principal).

En el caso de celdas de vista de tabla, la GPU puede verse embotellada con jerarquías de vistas complejas, porque en lugar de animar un bitmap, está animando la vista principal y haciendo cálculos de subvista, representando efectos de capa y componiendo todas las subvistas. Entonces, en lugar de animar un bitmap, es responsable de la relación del grupo de mapas de bits y de cómo interactúan para el mismo área de píxeles.

En resumen, la razón por la que dibujar su celda en una vista con gráficos centrales puede acelerar el desplazamiento de la tabla NO es porque se dibuje más rápido, sino porque reduce la carga en la GPU, que es el cuello de botella que le ocasiona problemas en ese escenario particular. .

Soy un desarrollador de juegos y hacía las mismas preguntas cuando mi amigo me dijo que mi jerarquía de vistas basada en UIImageView iba a ralentizar mi juego y hacerlo terrible. Luego procedí a investigar todo lo que pude encontrar sobre si usar UIViews, CoreGraphics, OpenGL o algo de terceros como Cocos2D. La respuesta consistente que recibí de amigos, profesores e ingenieros de Apple en la WWDC fue que no habrá mucha diferencia al final porque en algún nivel todos están haciendo lo mismo . Las opciones de nivel superior como UIViews confían en las opciones de nivel inferior como CoreGraphics y OpenGL, solo que están envueltas en código para que sea más fácil de usar.

No use CoreGraphics si acaba de volver a escribir el UIView. Sin embargo, puedes ganar algo de velocidad con CoreGraphics, siempre y cuando hagas todo tu dibujo en una vista, pero ¿realmente lo vales ? La respuesta que he encontrado es generalmente no. Cuando comencé mi juego, estaba trabajando con el iPhone 3G. A medida que mi juego creció en complejidad, comencé a ver un poco de retraso, pero con los dispositivos más nuevos era completamente imperceptible. Ahora tengo mucha acción pasando, y el único retraso parece ser una caída en 1-3 fps cuando se juega en el nivel más complejo en un iPhone 4.

Aún así, decidí usar Instruments para encontrar las funciones que más tiempo me tomaban. Descubrí que los problemas no estaban relacionados con mi uso de UIViews . En cambio, repetidamente llamaba a CGRectMake para ciertos cálculos de detección de colisión y cargaba archivos de imagen y audio por separado para ciertas clases que usan las mismas imágenes, en lugar de tener que extraer de una clase de almacenamiento central.

Así que, al final, es posible que pueda obtener una pequeña ganancia con el uso de CoreGraphics, pero por lo general no valdrá la pena o puede que no tenga ningún efecto. La única vez que uso CoreGraphics es cuando dibujo formas geométricas en lugar de texto e imágenes.