web-dev-qa-db-fra.com

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

Pour clarifier le but de cette question: je sais COMMENT créer des vues compliquées avec les deux sous-vues et en utilisant drawRect. J'essaie de bien comprendre quand et pourquoi utiliser l'un sur l'autre.

Je comprends également qu'il n'est pas logique d'optimiser autant à l'avance et de faire quelque chose de plus difficile avant de faire un profilage. Considérez que je suis à l'aise avec les deux méthodes et que je veux maintenant vraiment une compréhension plus profonde.

Une grande partie de ma confusion vient d'apprendre à rendre les performances de défilement de la vue de table vraiment fluides et rapides. Bien sûr, la source originale de cette méthode provient de auteur derrière Twitter pour iPhone (anciennement tweetie). Fondamentalement, il est dit que pour rendre le défilement du tableau fluide, le secret est de NE PAS utiliser de sous-vues, mais de faire tout le dessin dans une vue personnalisée. Essentiellement, il semble que l'utilisation de nombreuses sous-vues ralentit le rendu, car elles ont beaucoup de frais généraux et sont constamment recomposées sur leurs vues parentes.

Pour être honnête, cela a été écrit lorsque le 3GS était assez flambant neuf, et les iDevices sont devenus beaucoup plus rapides depuis lors. Toujours cette méthode est régulièrementsuggéré sur le interwebs et ailleurs pour les tables hautes performances. En fait, c'est une méthode suggérée dans Apple's Table Sample Code , a été suggérée dans plusieurs vidéos de la WWDC ( Dessin pratique pour les développeurs iOS ), et dans de nombreux iOS livres de programmation .

Il existe même outils impressionnants pour concevoir des graphiques et générer du code Core Graphics pour eux.

Donc, au début, je pense que "il y a une raison pour laquelle Core Graphics existe. C'est RAPIDE!"

Mais dès que je pense avoir l'idée "Favoriser les graphiques de base lorsque cela est possible", je commence à voir que drawRect est souvent responsable d'une mauvaise réactivité dans une application, est extrêmement coûteux en mémoire et taxe vraiment le processeur. Fondamentalement, je devrais " éviter de remplacer drawRect " (WWDC 2012 Performances de l'application iOS: graphiques et animations )

Donc je suppose que, comme tout, c'est compliqué. Vous pouvez peut-être m'aider moi-même et aider les autres à comprendre quand et pourquoi utiliser drawRect?

Je vois quelques situations évidentes pour utiliser Core Graphics:

  1. Vous disposez de données dynamiques (exemple du graphique boursier d'Apple)
  2. Vous avez un élément d'interface utilisateur flexible qui ne peut pas ê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 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 CG ne vaut pas le gain
  3. Vous souhaitez mettre à jour des parties de la vue sans redessiner le tout
  4. La disposition de vos sous-vues doit être mise à jour lorsque la taille de la vue parent change

Accordez donc vos connaissances. Dans quelles situations atteignez-vous pour drawRect/Core Graphics (qui pourrait également être accompli avec des sous-vues)? Quels facteurs vous ont conduit à cette décision? Comment/pourquoi le dessin dans une vue personnalisée est-il recommandé pour le défilement des cellules du tableau lisse, mais 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 les créez-vous avec CG vs utiliser une image png redimensionnable)?

Une compréhension approfondie de ce sujet n'est peut-être pas nécessaire pour créer des applications intéressantes, mais je n'aime pas choisir entre les techniques sans pouvoir expliquer pourquoi. Mon cerveau se fâche contre moi.

Mise à jour des questions

Merci pour l'information tout le monde. Quelques questions de clarification ici:

  1. Si vous dessinez quelque chose avec des graphiques de base, mais pouvez accomplir la même chose avec UIImageViews et un png pré-rendu, devriez-vous toujours suivre cette voie?
  2. Une question similaire: Surtout avec outils badass comme celui-ci , quand devriez-vous envisager de dessiner des éléments d'interface dans les graphiques principaux? (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 ma compréhension dans ma réponse ci-dessous, les mêmes gains de performances pour une cellule de tableau pourraient-ils être obtenus en capturant efficacement une image 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? Évidemment, certaines pièces devraient être élaborées. Juste une pensée intéressante que j'avais.
138
Bob Spryn

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 faciliter la maintenance. Utilisez Core Graphics lorsque vous ne pouvez pas obtenir les performances dont vous avez besoin avec UIKit, ou si vous savez essayer de pirater ensemble des effets de dessin dans UIKit serait plus compliqué.

Le flux de travail général doit consister à créer les vues de table avec des sous-vues. Utilisez des instruments pour mesurer la fréquence d'images sur le matériel le plus ancien pris en charge par votre application. Si vous ne pouvez pas obtenir 60 images par seconde, accédez à CoreGraphics. Lorsque vous avez fait cela pendant un certain temps, vous comprenez quand UIKit est probablement une perte de temps.

Alors, pourquoi Core Graphics est-il rapide?

CoreGraphics n'est pas vraiment rapide. S'il est utilisé tout le temps, vous allez probablement lentement. C'est une API de dessin riche, qui nécessite que son travail soit effectué sur le processeur, par opposition à beaucoup de travail UIKit qui est déchargé sur le GPU. Si vous deviez animer une balle se déplaçant sur l'écran, ce serait une terrible 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 un calque séparé.

L'autre problème est que lorsque vous ne faites pas de dessin personnalisé avec drawRect, UIKit peut optimiser les vues de stock, donc drawRect est un no-op, ou il peut prendre des raccourcis avec la composition. Lorsque vous remplacez drawRect, UIKit doit emprunter le chemin lent car 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 de vue de table. Après l'appel de drawRect lorsqu'une vue apparaît pour la première fois à l'écran, le contenu est mis en cache et le défilement est une simple traduction effectuée par le GPU. Parce que vous avez affaire à une seule vue, plutôt qu'à une hiérarchie complexe, les optimisations drawRect d'UIKit deviennent moins importantes. Ainsi, le goulot d'étranglement devient combien 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. Lorsqu'il y a une incitation, optimisez.

71
Ben Sandofsky

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 montrant une telle 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 par-dessus d'autres images.

CoreGraphics/Quartz est une API pour générer images. Il prend un tampon de pixels (encore une fois, pensez à la texture OpenGL) et change les pixels individuels à l'intérieur. Tout cela se produit dans RAM et sur le CPU, et seulement une fois Quartz terminé, l'image est "rincée" vers le GPU. Ce trajet aller-retour pour obtenir une image du GPU, le changer, puis télécharger l'image entière (ou au moins une partie relativement grande) vers le GPU est plutôt lent. De plus, le dessin réel que Quartz fait, bien que très rapide pour ce que vous faites, est beaucoup plus lent que ce que le GPU fait.

C'est évident, étant donné que le GPU se déplace principalement autour de pixels inchangés en gros morceaux. Quartz fait un accès aléatoire aux pixels et partage le CPU avec la mise en réseau, l'audio, etc. De plus, si vous avez plusieurs éléments que vous dessinez en utilisant Quartz en même temps, vous devez tous les redessiner quand on change, puis télécharger le morceau entier, alors que si vous changez une image et laissez ensuite 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 sur le GPU.

Lorsque vous n'implémentez pas -drawRect:, la plupart des vues peuvent simplement être optimisées. Ils ne contiennent aucun pixel, ils ne peuvent donc pas dessiner quoi que ce soit. D'autres vues, comme UIImageView, ne dessinent qu'un UIImage (qui, encore une fois, est essentiellement une référence à une texture, qui a probablement déjà été chargée sur le GPU). Donc, si vous dessinez le même UIImage 5 fois à l'aide d'un UIImageView, il n'est téléchargé qu'une seule fois sur le GPU, puis dessiné sur l'écran dans 5 emplacements différents, ce qui nous fait gagner du temps et du CPU.

Lorsque vous implémentez -drawRect :, cela provoque la création d'une nouvelle image. Vous tirez ensuite sur cela sur le CPU en utilisant Quartz. Si vous dessinez un UIImage dans votre drawRect, il télécharge probablement l'image à partir du GPU, la copie dans l'image sur laquelle vous dessinez, et une fois que vous avez terminé, la télécharge deuxième copie de l'image retour à la carte graphique. Vous utilisez donc deux fois la mémoire GPU de l'appareil.

Ainsi, le moyen le plus rapide pour dessiner est généralement de garder le contenu statique séparé du contenu changeant (dans des sous-classes UIViews/UIView/CALayers distinctes). Chargez du contenu statique en tant que UIImage et dessinez-le à l'aide d'un UIImageView et placez le contenu généré dynamiquement lors de l'exécution dans un drawRect. Si vous avez du contenu qui est dessiné à plusieurs reprises, mais qui ne change pas en soi (c'est-à-dire 3 icônes qui apparaissent dans le même emplacement pour indiquer un certain état), utilisez également UIImageView.

Une mise en garde: il y a une telle chose comme avoir trop de UIViews. Les zones particulièrement transparentes coûtent plus cher au GPU pour dessiner, 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 un UIView comme "opaque", pour indiquer au GPU qu'il peut simplement tout effacer derrière cette image.

Si votre contenu est généré dynamiquement au moment de l'exécution mais reste le même pendant la durée de vie de l'application (par exemple, une étiquette contenant le nom d'utilisateur), il peut être judicieux de simplement dessiner le tout une fois en utilisant Quartz, avec le texte, le bordure de bouton, etc., dans le cadre de l'arrière-plan. Mais c'est généralement une optimisation qui n'est pas nécessaire à moins que l'application Instruments ne vous le dise différemment.

51
uliwitness

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

Approche générale

Il est assez 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 goulots d'étranglement possibles dans un iDevice, le CPU et le GPU
  2. Le CPU est responsable du dessin/rendu initial d'une vue
  3. Le GPU est responsable de la majorité de l'animation (Core Animation), des effets de calque, de la composition, etc.
  4. UIView a beaucoup d'optimisations, de mise en cache, etc., intégrées pour gérer les hiérarchies de vues complexes
  5. Lorsque vous remplacez drawRect, vous passez à côté de nombreux avantages fournis par UIView, et c'est généralement plus lent que de laisser UIView gérer le rendu.

Le dessin du contenu des cellules dans un UIView plat peut grandement améliorer votre FPS sur les tables de défilement.

Comme je l'ai dit ci-dessus, le CPU et le GPU sont deux goulots d'étranglement possibles. Puisqu'ils gèrent généralement des choses différentes, vous devez faire attention au goulot d'étranglement que vous rencontrez. Dans le cas des tableaux déroulants, ce n'est pas que Core Graphics dessine plus vite, et c'est pourquoi cela peut grandement améliorer votre FPS.

En fait, 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 du défilement saccadé soit que vous goulot d'étranglement du GPU, vous devez donc y remédier.

Pourquoi remplacer drawRect (en utilisant des graphiques de base) peut aider le défilement du tableau:

D'après ce que je comprends, le GPU n'est pas responsable du rendu initial des vues, mais est à la place des textures ou des bitmaps, parfois avec certaines propriétés de couche, après leur rendu. Il est ensuite responsable de la composition des bitmaps, du rendu de tous ces effets de calque et de la majorité de l'animation (Core Animation).

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

Donc, en résumé, la raison pour laquelle dessiner votre cellule dans une vue avec des graphiques de base peut accélérer le défilement de votre tableau n'est PAS parce qu'elle dessine plus rapidement, mais parce qu'elle réduit la charge sur le GPU, ce qui est le goulot d'étranglement qui vous pose des problèmes dans ce scénario particulier.

26
Bob Spryn

Je suis développeur de jeux et je 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 procédé à des recherches sur tout ce que je pouvais trouver pour utiliser UIViews, CoreGraphics, OpenGL ou quelque chose de tiers comme Cocos2D. La réponse cohérente que j'ai reçue d'amis, d'enseignants et Apple ingénieurs de la WWDC était qu'il n'y aura pas beaucoup de différence à la fin parce que à un certain niveau, ils le font tous la même chose. Les options de niveau supérieur comme UIViews s'appuient sur les options de niveau inférieur comme CoreGraphics et OpenGL, juste elles sont enveloppées dans du code pour le rendre plus facile à utiliser.

N'utilisez pas CoreGraphics si vous allez finir par réécrire l'UIView. Cependant, vous pouvez gagner un peu de vitesse en utilisant CoreGraphics, tant que vous faites tout votre dessin dans une seule vue, mais est-ce vraiment vaut cela? La réponse que j'ai trouvée est généralement non. Quand j'ai commencé mon jeu, je travaillais avec l'iPhone 3G. Alors que mon jeu devenait de plus en plus complexe, j'ai commencé à voir un certain décalage, mais avec les nouveaux appareils, il était complètement imperceptible. Maintenant, j'ai beaucoup d'action en cours, et le seul décalage semble être une baisse de 1 à 3 images par seconde lorsque vous jouez au niveau le plus complexe sur un iPhone 4.

J'ai quand même décidé d'utiliser des instruments pour trouver les fonctions qui prenaient le plus de temps. J'ai trouvé que les problèmes n'étaient pas liés à mon utilisation de UIViews. Au lieu de cela, il appelait à plusieurs reprises CGRectMake pour certains calculs de détection de collision et chargeait séparément les fichiers image et audio pour certaines classes qui utilisent les mêmes images, plutôt que de les faire puiser dans une classe de stockage centrale.

Donc, à la fin, vous pourriez être en mesure d'obtenir un léger gain en utilisant CoreGraphics, mais généralement 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.

6
WolfLink