138 votes

drawRect ou non (quand faut-il utiliser drawRect/Core Graphics ou subviews/images et pourquoi ?)

Pour clarifier le but de cette question : Je sais COMMENT créer des vues complexes avec des sous-vues et en utilisant drawRect. J'essaie de bien comprendre quand et pourquoi utiliser l'une plutôt que l'autre.

Je comprends également que cela n'a pas de sens d'optimiser autant à l'avance, et de faire quelque chose de la manière la plus difficile avant de faire un profilage. Je considère que je suis à l'aise avec les deux méthodes, et que je veux vraiment approfondir mes connaissances.

Une grande partie de ma confusion vient du fait que j'ai appris comment rendre le défilement des vues de tableaux vraiment fluide et rapide. Bien sûr, la source originale de cette méthode provient de l'application auteur derrière twitter pour iPhone (anciennement tweetie). En gros, il est dit que pour rendre le défilement des tableaux plus fluide, le secret est de ne PAS utiliser de sous-vues, mais plutôt de faire tout le dessin dans une uiview personnalisée. Il semble essentiellement que l'utilisation d'un grand nombre de vues secondaires ralentit le rendu parce qu'elles ont beaucoup de frais généraux et sont constamment recomposées par rapport à leurs vues parentes.

Pour être juste, ceci a été écrit quand le 3GS était tout neuf, et les iDevices sont devenus beaucoup plus rapides depuis. Toujours cette méthode es régulièrement Proposition de sur le interwebs et ailleurs pour les tables à haute performance. En fait, c'est une méthode suggérée dans Exemple de code pour la table des pommes a été suggéré dans plusieurs vidéos de la WWDC ( Dessin pratique pour les développeurs iOS ), et beaucoup d'iOS livres de programmation .

Il y a même des outils impressionnants pour concevoir des graphiques et générer du code Core Graphics pour ceux-ci.

Donc, au début, j'ai été amené à croire qu'il y a une raison pour laquelle le Core Graphics existe. C'est RAPIDE !"

Mais dès que je pense avoir compris l'idée "Favoriser le Core Graphics quand c'est possible", je commence à voir que drawRect est souvent responsable de la mauvaise réactivité d'une application, est extrêmement coûteux en mémoire, et taxe vraiment le CPU. En gros, je devrais " Évitez de surcharger drawRect "(WWDC 2012 Performance des applications iOS : Graphiques et animations )

Donc je suppose que, comme tout, c'est compliqué. Peut-être pouvez-vous nous aider, moi et d'autres, à comprendre quand et pourquoi utiliser drawRect ?

Je vois quelques situations évidentes pour utiliser le Core Graphics :

  1. Vous avez des données dynamiques (exemple du graphique boursier d'Apple)
  2. Vous avez un élément d'interface utilisateur flexible qui ne peut être exécuté avec une simple image redimensionnable.
  3. Vous créez un graphique dynamique qui, une fois rendu, est utilisé à plusieurs endroits.

Je vois des situations pour éviter le Core Graphics :

  1. Les propriétés de votre vue doivent être animées séparément
  2. Vous avez une hiérarchie de vues relativement petite, donc tout effort supplémentaire perçu en utilisant la CG ne vaut pas le gain.
  3. Vous voulez mettre à jour des parties de la vue sans redessiner l'ensemble.
  4. La mise en page de vos vues secondaires doit être mise à jour lorsque la taille de la vue parent change.

Alors donnez votre savoir. Dans quelles situations utilisez-vous drawRect/Core Graphics (qui pourrait aussi être réalisé avec des subviews) ? Quels facteurs vous amènent à cette décision ? Comment/Pourquoi dessiner dans une vue personnalisée est recommandé pour un défilement fluide des cellules d'un tableau, alors qu'Apple déconseille drawRect pour des raisons de performances en général ? Qu'en est-il des images d'arrière-plan simples (quand faut-il les créer avec CG ou utiliser une image png redimensionnable) ?

Une compréhension approfondie de ce sujet n'est peut-être pas nécessaire pour réaliser des applications valables, mais je n'aime pas choisir entre les techniques sans être capable d'expliquer pourquoi. Mon cerveau se met en colère contre moi.

Mise à jour des questions

Merci à tous pour ces informations. Quelques questions de clarification ici :

  1. Si vous dessinez quelque chose avec des graphiques de base, mais que vous pouvez accomplir la même chose avec UIImageViews et un png pré-rendu, devez-vous toujours choisir cette voie ?
  2. Une question similaire : Surtout avec des outils qui déchirent comme ça Quand devriez-vous envisager de dessiner les éléments d'interface dans les graphiques de base ? (Probablement lorsque l'affichage de votre élément est variable. Par exemple, un bouton avec 20 variations de couleurs différentes. D'autres cas ?)
  3. Compte tenu de ce que j'ai compris dans ma réponse ci-dessous, les mêmes gains de performance pour une cellule de tableau pourraient-ils être obtenus en capturant effectivement un instantané bitmap de votre cellule après le rendu de votre UIView complexe, et en l'affichant tout en faisant défiler et en masquant votre vue complexe ? Il est évident que certains éléments devraient être mis au point. C'est juste une idée intéressante que j'ai eue.

71voto

Ben Sandofsky Points 351

Restez fidèle à UIKit et aux sous-vues chaque fois que vous le pouvez. Vous pouvez être plus productif, et profiter de tous les mécanismes OO qui devraient rendre les choses plus faciles à maintenir. Utilisez Core Graphics lorsque vous ne pouvez pas obtenir les performances dont vous avez besoin avec UIKit, ou lorsque vous savez qu'il serait plus compliqué d'essayer de bricoler des effets de dessin dans UIKit.

Le flux de travail général devrait consister à construire les vues de table avec des vues secondaires. Utilisez Instruments pour mesurer le taux de rafraîchissement sur le matériel le plus ancien que votre application supportera. Si vous ne pouvez pas obtenir 60fps, passez à CoreGraphics. Lorsque vous avez fait cela pendant un certain temps, vous avez une idée de quand UIKit est probablement une perte de temps.

Alors, pourquoi le Core Graphics est-il rapide ?

CoreGraphics n'est pas vraiment rapide. S'il est utilisé en permanence, vous allez probablement être lent. Il s'agit d'une API de dessin riche, qui nécessite que son travail soit effectué sur le CPU, contrairement à une grande partie du travail de UIKit qui est déchargé sur le GPU. Si vous deviez animer une balle se déplaçant à l'écran, ce serait une très mauvaise idée d'appeler setNeedsDisplay sur une vue 60 fois par seconde. Donc, si vous avez des sous-composants de votre vue qui doivent être animés individuellement, chaque composant doit être une couche séparée.

L'autre problème est que lorsque vous ne faites pas de dessin personnalisé avec drawRect, UIKit peut optimiser les vues stockées de sorte que drawRect ne soit pas utilisé, ou il peut prendre des raccourcis avec le compositing. Lorsque vous remplacez drawRect, UIKit doit prendre le chemin le plus lent parce qu'il n'a aucune idée de ce que vous faites.

Ces deux problèmes peuvent être compensés par des avantages dans le cas des cellules en vue tableau. Après l'appel de drawRect lors de la première apparition d'une vue à l'écran, le contenu est mis en cache et le défilement est une simple traduction effectuée par le GPU. Étant donné que vous avez affaire à une seule vue, plutôt qu'à une hiérarchie complexe, les optimisations de drawRect de UIKit deviennent moins importantes. Le goulot d'étranglement devient donc la mesure dans laquelle vous pouvez optimiser votre dessin Core Graphics.

Chaque fois que vous le pouvez, utilisez UIKit. Faites l'implémentation la plus simple qui fonctionne. Profil. Quand il y a une incitation, optimisez.

52voto

uliwitness Points 2085

La différence est que UIView et CALayer traitent essentiellement des images fixes. Ces images sont téléchargées sur la carte graphique (si vous connaissez OpenGL, considérez une image comme une texture, et une UIView/CALayer comme un polygone représentant cette texture). Une fois qu'une image est sur le GPU, elle peut être dessinée très rapidement, et même plusieurs fois, et (avec une légère pénalité de performance) même avec des niveaux variables de transparence alpha au-dessus d'autres images.

CoreGraphics/Quartz est une API pour générant images. Il prend un tampon de pixels (encore une fois, pensez à la texture OpenGL) et modifie les pixels individuels à l'intérieur. Tout cela se passe en RAM et sur le CPU, et ce n'est qu'une fois que Quartz a terminé que l'image est renvoyée vers le GPU. Cet aller-retour consistant à obtenir une image du GPU, à la modifier, puis à renvoyer l'image entière (ou du moins une partie relativement importante de celle-ci) au GPU est plutôt lent. De plus, le dessin que fait Quartz, bien que très rapide pour ce que vous faites, est beaucoup plus lent que ce que fait le GPU.

C'est évident, étant donné que le GPU déplace principalement des pixels inchangés en gros morceaux. Quartz fait un accès aléatoire aux pixels et partage le CPU avec le réseau, l'audio, etc. De plus, si vous dessinez plusieurs éléments en même temps à l'aide de Quartz, vous devez tous les redessiner lorsque l'un d'entre eux change, puis télécharger le morceau entier, alors que si vous modifiez une image et que vous laissez UIViews ou CALayers la coller sur vos autres images, vous pouvez vous en sortir en téléchargeant des quantités de données beaucoup plus petites vers le GPU.

Si vous n'implémentez pas -drawRect :, la plupart des vues peuvent simplement être optimisées. Elles ne contiennent pas de pixels et ne peuvent donc rien dessiner. D'autres vues, comme UIImageView, ne dessinent qu'une UIImage (qui, encore une fois, est essentiellement une référence à une texture, qui a probablement déjà été chargée sur le GPU). Ainsi, si vous dessinez la même UIImage 5 fois à l'aide d'une UIImageView, elle n'est chargée qu'une seule fois sur le GPU, puis affichée à l'écran à 5 endroits différents, ce qui nous fait gagner du temps et économiser le CPU.

Lorsque vous implémentez -drawRect :, une nouvelle image est créée. Vous dessinez ensuite dans cette image sur le CPU en utilisant Quartz. Si vous dessinez une UIImage dans votre drawRect, il est probable que l'image soit téléchargée depuis le GPU, copiée dans l'image sur laquelle vous dessinez et, une fois que vous avez terminé, téléchargée dans cette image. deuxième exemplaire de l'image vers la carte graphique. Vous utilisez donc deux fois la mémoire du GPU sur l'appareil.

Ainsi, le moyen le plus rapide de dessiner est généralement de séparer le contenu statique du contenu changeant (dans des UIViews/sous-classes UIView/CALayers distincts). Chargez le contenu statique sous forme d'UIImage et dessinez-le à l'aide d'un UIImageView, et placez le contenu généré dynamiquement au moment de l'exécution dans un drawRect. Si vous avez un contenu qui est dessiné de manière répétée, mais qui ne change pas en soi (par exemple, 3 icônes qui sont affichées dans le même emplacement pour indiquer un état), utilisez également UIImageView.

Une mise en garde : il est possible d'avoir trop d'UIViews. Les zones particulièrement transparentes sont plus difficiles à dessiner pour le GPU, car elles doivent être mélangées avec d'autres pixels derrière elles lorsqu'elles sont affichées. C'est pourquoi vous pouvez marquer une UIView comme "opaque", pour indiquer au GPU qu'il peut simplement effacer tout ce qui se trouve derrière cette image.

Si votre contenu est généré dynamiquement au moment de l'exécution, mais reste le même pendant toute la durée de vie de l'application (par exemple, une étiquette contenant le nom de l'utilisateur), il peut être judicieux de ne dessiner l'ensemble qu'une seule fois à l'aide de Quartz, avec le texte, la bordure du bouton, etc. Mais c'est généralement une optimisation qui n'est pas nécessaire, à moins que l'application Instruments ne vous dise le contraire.

26voto

Bob Spryn Points 6886

Je vais essayer de garder un résumé de ce que j'extrapole des réponses des autres ici, et poser des questions de clarification dans une mise à jour de la question originale. Mais j'encourage les autres à continuer à répondre et à voter pour ceux qui ont fourni de bonnes informations.

Approche générale

Il est tout à fait clair que l'approche générale, comme Ben Sandofsky l'a mentionné dans sa réponse devrait être "Chaque fois que vous le pouvez, utilisez UIKit. Faites l'implémentation la plus simple qui fonctionne. Profil. Quand il y a une incitation, optimisez."

Le pourquoi

  1. Il existe deux principaux goulets d'étranglement possibles dans un iDevice, le CPU et le GPU
  2. L'unité centrale est responsable du dessin/rendu initial d'une vue.
  3. Le GPU est responsable de la majorité des animations (Core Animation), des effets de couche, du compositing, etc.
  4. UIView dispose de nombreuses optimisations, de la mise en cache, etc., pour gérer les hiérarchies de vues complexes.
  5. En surchargeant drawRect, vous ne bénéficiez pas d'un grand nombre d'avantages offerts par UIView, et c'est généralement plus lent que de laisser UIView gérer le rendu.

Le fait de dessiner le contenu des cellules dans un UIView plat peut améliorer considérablement votre FPS sur les tableaux défilants.

Comme je l'ai dit plus haut, le CPU et le GPU sont deux goulots d'étranglement possibles. Comme ils gèrent généralement des choses différentes, vous devez faire attention à quel goulot d'étranglement vous vous heurtez. Dans le cas des tableaux défilants, ce n'est pas que le Core Graphics dessine plus rapidement, et c'est pourquoi il peut améliorer considérablement votre FPS.

En fait, le Core Graphics peut très bien être plus lent qu'une hiérarchie UIView imbriquée pour le rendu initial. Cependant, il semble que la raison typique d'un défilement irrégulier soit l'engorgement du GPU, et vous devez donc y remédier.

Pourquoi la surcharge de drawRect (en utilisant le noyau graphique) peut aider le défilement des tableaux :

D'après ce que j'ai compris, le GPU n'est pas responsable du rendu initial des vues, mais reçoit des textures, ou des bitmaps, parfois avec des propriétés de couche, après qu'elles ont été rendues. Il est ensuite responsable de la composition des bitmaps, du rendu de tous les effets de couche et de la majorité de l'animation (animation principale).

Dans le cas des cellules de la vue tableau, le GPU peut être engorgé par des hiérarchies de vues complexes, car au lieu d'animer un bitmap, il anime la vue parent et effectue les calculs de disposition des sous-images, le rendu des effets de couche et la composition de toutes les sous-images. Ainsi, au lieu d'animer un bitmap, il est responsable de la relation entre plusieurs bitmaps et de leur interaction, pour la même zone de pixels.

En résumé, la raison pour laquelle le dessin de votre cellule dans une vue avec des graphiques de base peut accélérer le défilement de votre table n'est PAS parce que le dessin est plus rapide, mais parce qu'il réduit la charge sur le GPU, qui est le goulot d'étranglement qui vous pose problème dans ce scénario particulier.

6voto

WolfLink Points 1749

Je suis développeur de jeux et je me posais les mêmes questions lorsque mon ami m'a dit que ma hiérarchie de vues basée sur UIImageView allait ralentir mon jeu et le rendre terrible. J'ai ensuite fait des recherches sur tout ce que je pouvais trouver pour savoir s'il fallait utiliser UIViews, CoreGraphics, OpenGL ou quelque chose de tiers comme Cocos2D. La réponse constante que j'ai obtenue de mes amis, de mes professeurs et des ingénieurs d'Apple à la WWDC est qu'il n'y aura pas beaucoup de différence au final car à un certain niveau, ils font tous la même chose. . Les options de plus haut niveau comme UIViews reposent sur les options de plus bas niveau comme CoreGraphics et OpenGL, mais elles sont enveloppées dans du code pour faciliter leur utilisation.

N'utilisez pas CoreGraphics si vous devez finir par réécrire l'UIView. Cependant, vous pouvez gagner un peu de vitesse en utilisant CoreGraphics, tant que vous faites tous vos dessins dans une seule vue, mais est-ce vraiment le cas ? qui vaut le ? La réponse que j'ai trouvée est généralement non. Lorsque j'ai commencé mon jeu, je travaillais avec l'iPhone 3G. Au fur et à mesure que mon jeu devenait plus complexe, j'ai commencé à voir un certain décalage, mais avec les nouveaux appareils, il était complètement imperceptible. Aujourd'hui, il y a beaucoup d'action, et le seul décalage semble être une baisse de 1 à 3 images par seconde lorsque je joue dans le niveau le plus complexe sur un iPhone 4.

J'ai tout de même décidé d'utiliser des instruments pour trouver les fonctions qui prenaient le plus de temps. J'ai découvert que les problèmes n'étaient pas liés à mon utilisation de UIViews . Au lieu de cela, il appelait de manière répétée CGRectMake pour certains calculs de détection de collision et chargeait les fichiers image et audio séparément pour certaines classes qui utilisent les mêmes images, au lieu de les faire tirer d'une classe de stockage centrale.

Au bout du compte, vous pourrez peut-être obtenir un léger gain en utilisant CoreGraphics, mais en général, cela n'en vaudra pas la peine ou n'aura aucun effet. La seule fois où j'utilise CoreGraphics, c'est pour dessiner des formes géométriques plutôt que du texte et des images.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X