web-dev-qa-db-de.com

drawRect oder nicht drawRect (wann sollte man drawRect / Core Graphics vs subviews / images verwenden und warum?)

Um den Zweck dieser Frage zu klären: Ich weiß, wie man komplizierte Ansichten mit beiden Unteransichten und mit drawRect erstellt. Ich versuche, das Wann und Warum einer über dem anderen zu verstehen.

Ich verstehe auch, dass es keinen Sinn macht, so viel im Voraus zu optimieren und etwas Schwierigeres zu tun, bevor man ein Profil erstellt. Bedenken Sie, dass ich mit beiden Methoden vertraut bin und jetzt wirklich ein tieferes Verständnis möchte.

Ein großer Teil meiner Verwirrung rührt daher, dass ich gelernt habe, wie man die Bildlaufleistung von Tabellenansichten wirklich reibungslos und schnell macht. Die ursprüngliche Quelle dieser Methode ist natürlich Autor hinter Twitter für iPhone (früher Tweetie). Grundsätzlich heißt es, um das Scrollen in der Tabelle reibungslos zu gestalten, sollten keine Unteransichten verwendet werden, sondern alle Zeichnungen in einer benutzerdefinierten Ansicht. Im Wesentlichen Die Verwendung vieler Unteransichten scheint das Rendern zu verlangsamen, da sie viel Overhead haben und über ihre übergeordneten Ansichten ständig neu zusammengesetzt werden.

Um fair zu sein, dies wurde geschrieben, als das 3GS ziemlich brandneu war und iDevices seitdem viel schneller geworden sind. Trotzdem ist diese Methoderegelmäßigempfohlen in Interwebs und an anderer Stelle für Hochleistungstabellen. Tatsächlich ist es eine vorgeschlagene Methode in Apple's Table Sample Code , die in mehreren WWDC-Videos ( Praktisches Zeichnen für iOS-Entwickler ) und vielen iOS Programmierbüchern) vorgeschlagen wurde .

Es gibt sogar fantastisch aussehende Werkzeuge , um Grafiken zu entwerfen und Core Graphics-Code für sie zu generieren.

Ich muss also zunächst annehmen, dass "es einen Grund gibt, warum Core Graphics existiert. Es ist SCHNELL!"

Aber sobald ich denke, dass ich auf die Idee komme, "Core Graphics wann immer möglich zu favorisieren", stelle ich fest, dass drawRect häufig für eine schlechte Reaktionsfähigkeit in einer App verantwortlich ist, extrem teuer ist und die CPU wirklich belastet. Grundsätzlich sollte ich " Vermeiden Sie das Überschreiben von drawRect " (WWDC 2012 Leistung der iOS-App: Grafiken und Animationen )

Also denke ich, wie alles ist es kompliziert. Vielleicht können Sie mir und anderen helfen, das Wann und Warum für die Verwendung von drawRect zu verstehen?

Ich sehe ein paar offensichtliche Situationen, um Core Graphics zu verwenden:

  1. Sie haben dynamische Daten (Beispiel für Apple Stock Chart)
  2. Sie haben ein flexibles Benutzeroberflächenelement, das mit einem einfachen skalierbaren Bild nicht ausgeführt werden kann
  3. Sie erstellen eine dynamische Grafik, die nach dem Rendern an mehreren Stellen verwendet wird

Ich sehe Situationen, um Core Graphics zu vermeiden:

  1. Die Eigenschaften Ihrer Ansicht müssen separat animiert werden
  2. Sie haben eine relativ kleine Ansichtshierarchie, sodass sich ein wahrgenommener zusätzlicher Aufwand für die Verwendung von CG nicht lohnt
  3. Sie möchten Teile der Ansicht aktualisieren, ohne das Ganze neu zu zeichnen
  4. Das Layout Ihrer Unteransichten muss aktualisiert werden, wenn sich die Größe der übergeordneten Ansicht ändert

Schenke also dein Wissen. In welchen Situationen greifen Sie zu drawRect/Core Graphics (dies könnte auch mit Unteransichten erreicht werden)? Welche Faktoren führen Sie zu dieser Entscheidung? Wie/warum wird das Zeichnen in einer benutzerdefinierten Ansicht für das Scrollen in butterweichen Tabellenzellen empfohlen, aber Apple rät drawRect im Allgemeinen aus Leistungsgründen ab? Was ist mit einfachen Hintergrundbildern (wann erstellen Sie sie mit CG im Vergleich zu einem skalierbaren PNG-Bild)?

Ein tiefes Verständnis dieses Themas ist möglicherweise nicht erforderlich, um lohnende Apps zu erstellen, aber ich mag es nicht, zwischen Techniken zu wählen, ohne erklären zu können, warum. Mein Gehirn wird sauer auf mich.

Frage Update

Danke für die Information an alle. Einige klärende Fragen hier:

  1. Wenn Sie etwas mit Kerngrafiken zeichnen, aber dasselbe mit UIImageViews und einem vorgerenderten PNG erreichen können, sollten Sie immer diesen Weg gehen?
  2. Eine ähnliche Frage: Vor allem mit Badass-Tools wie dieses , wann sollten Sie erwägen, Oberflächenelemente in Kerngrafiken zu zeichnen? (Wahrscheinlich, wenn die Anzeige Ihres Elements variabel ist. Zum Beispiel eine Schaltfläche mit 20 verschiedenen Farbvarianten. Gibt es noch andere Fälle?)
  3. Könnten nach meinem Verständnis in der nachstehenden Antwort möglicherweise die gleichen Leistungssteigerungen für eine Tabellenzelle erzielt werden, indem eine Momentaufnahme-Bitmap Ihrer Zelle nach dem Rendern von UIView selbst erfasst und angezeigt wird, während Sie durch die komplexe Ansicht scrollen und sie ausblenden? Offensichtlich müssten einige Stücke herausgearbeitet werden. Nur ein interessanter Gedanke, den ich hatte.
138
Bob Spryn

Halten Sie sich an UIKit und Unteransichten, wann immer Sie können. Sie können produktiver sein und alle OO) - Mechanismen nutzen, die die Wartung erleichtern. Verwenden Sie Core Graphics, wenn Sie mit UIKit nicht die Leistung erzielen, die Sie benötigen oder wissen Der Versuch, Zeichnungseffekte in UIKit zu hacken, wäre komplizierter.

Der allgemeine Workflow sollte darin bestehen, die Tabellenansichten mit Unteransichten zu erstellen. Verwenden Sie Instrumente, um die Bildrate auf der ältesten Hardware zu messen, die Ihre App unterstützt. Wenn Sie keine 60fps erhalten können, wechseln Sie zu CoreGraphics. Wenn Sie dies für eine Weile getan haben, bekommen Sie ein Gefühl dafür, wann UIKit wahrscheinlich eine Zeitverschwendung ist.

Warum ist Core Graphics so schnell?

CoreGraphics ist nicht sehr schnell. Wenn es die ganze Zeit verwendet wird, werden Sie wahrscheinlich langsam. Es ist eine reichhaltige Zeichnungs-API, deren Arbeit auf der CPU erledigt werden muss, im Gegensatz zu viel UIKit-Arbeit, die auf die GPU verlagert wird. Wenn Sie eine Kugel animieren müssten, die sich über den Bildschirm bewegt, wäre es eine schreckliche Idee, setNeedsDisplay 60-mal pro Sekunde für eine Ansicht aufzurufen. Wenn Sie also Unterkomponenten Ihrer Ansicht haben, die einzeln animiert werden müssen, sollte jede Komponente eine separate Ebene sein.

Das andere Problem ist, dass UIKit, wenn Sie kein benutzerdefiniertes Zeichnen mit drawRect ausführen, die Bestandsansichten optimieren kann, sodass drawRect ein No-Op ist, oder Verknüpfungen mit Compositing zulässt. Wenn Sie drawRect überschreiben, muss UIKit langsam vorgehen, da es keine Ahnung hat, was Sie tun.

Diese beiden Probleme können durch Vorteile bei Tabellensichtzellen aufgewogen werden. Nachdem drawRect aufgerufen wurde, wenn eine Ansicht zum ersten Mal auf dem Bildschirm angezeigt wird, wird der Inhalt zwischengespeichert, und das Scrollen ist eine einfache Übersetzung, die von der GPU ausgeführt wird. Da es sich nicht um eine komplexe Hierarchie, sondern um eine einzelne Ansicht handelt, verlieren die drawRect-Optimierungen von UIKit an Bedeutung. Der Engpass besteht also darin, wie weit Sie Ihre Core Graphics-Zeichnung optimieren können.

Wann immer Sie können, verwenden Sie UIKit. Führen Sie die einfachste Implementierung durch, die funktioniert. Profil. Wenn es einen Anreiz gibt, optimieren Sie.

71
Ben Sandofsky

Der Unterschied besteht darin, dass UIView und CALayer im Wesentlichen feste Bilder verarbeiten. Diese Bilder werden auf die Grafikkarte hochgeladen (wenn Sie OpenGL kennen, stellen Sie sich ein Bild als Textur und einen UIView/CALayer als Polygon mit einer solchen Textur vor). Sobald sich ein Bild auf der GPU befindet, kann es sehr schnell und sogar mehrmals gezeichnet werden und (mit einem leichten Leistungsverlust) auch mit unterschiedlichen Alphatransparenzstufen über anderen Bildern.

CoreGraphics/Quartz ist eine API für Erzeugen Bilder. Es benötigt einen Pixelpuffer (man denke wieder an OpenGL-Textur) und ändert einzelne Pixel darin. Dies alles geschieht in RAM und auf der CPU, und nur wenn Quarz fertig ist, wird das Bild zurück auf die GPU "gespült". Diese Runde des Abrufens eines Bildes von der GPU, Das Ändern und anschließende Hochladen des gesamten Bilds (oder zumindest eines vergleichsweise großen Teils davon) auf die GPU ist ziemlich langsam. Außerdem ist die eigentliche Zeichnung, die Quartz ausführt, zwar sehr schnell für das, was Sie tun, viel langsamer als was die GPU macht.

Das ist offensichtlich, wenn man bedenkt, dass sich die GPU in großen Blöcken hauptsächlich um unveränderte Pixel bewegt. Quartz greift willkürlich auf Pixel zu und teilt sich die CPU mit Netzwerken, Audio usw. Auch wenn Sie mehrere Elemente gleichzeitig mit Quartz zeichnen, müssen Sie alle neu zeichnen, wenn sich eines ändert, und dann das hochladen Wenn Sie ein Bild ändern und UIViews oder CALayers es dann in Ihre anderen Bilder einfügen lassen, können Sie weitaus geringere Datenmengen auf die GPU hochladen.

Wenn Sie -drawRect: nicht implementieren, können die meisten Ansichten einfach wegoptimiert werden. Sie enthalten keine Pixel, können also nichts zeichnen. Andere Ansichten, wie UIImageView, zeichnen nur ein UIImage (das wiederum im Wesentlichen auf eine Textur verweist, die wahrscheinlich bereits auf die GPU geladen wurde). Wenn Sie also dasselbe UIImage fünfmal mit einem UIImageView zeichnen, wird es nur einmal auf die GPU hochgeladen und dann an fünf verschiedenen Stellen auf die Anzeige gezeichnet, wodurch Zeit und CPU gespart werden.

Wenn Sie -drawRect: implementieren, wird ein neues Image erstellt. Sie zeichnen dann mit Quartz in die CPU. Wenn Sie ein UIImage in Ihrem drawRect zeichnen, lädt es wahrscheinlich das Bild von der GPU herunter, kopiert es in das Bild, in das Sie zeichnen, und lädt dieses zweite Kopie des Bildes hoch, sobald Sie fertig sind zurück zur Grafikkarte. Sie verwenden also den doppelten GPU-Speicher auf dem Gerät.

Der schnellste Weg, um zu zeichnen, besteht normalerweise darin, statische Inhalte von sich ändernden Inhalten zu trennen (in separaten UIViews/UIView-Unterklassen/CALayern). Laden Sie statischen Inhalt als UIImage und zeichnen Sie ihn mit einem UIImageView. Fügen Sie zur Laufzeit dynamisch generierten Inhalt in ein drawRect ein. Wenn Sie Inhalte haben, die wiederholt gezeichnet werden, sich aber selbst nicht ändern (d. H. 3 Symbole, die im selben Slot angezeigt werden, um den Status anzuzeigen), verwenden Sie auch UIImageView.

Eine Einschränkung: Es gibt so etwas wie zu viele UIViews. Besonders transparente Bereiche beanspruchen die GPU beim Zeichnen stärker, da sie bei der Anzeige mit anderen dahinterliegenden Pixeln gemischt werden müssen. Aus diesem Grund können Sie eine UIView als "undurchsichtig" markieren, um der GPU mitzuteilen, dass sie nur alles hinter diesem Image auslöschen kann.

Wenn Sie Inhalte haben, die zur Laufzeit dynamisch generiert werden, aber für die Dauer der Anwendungslebensdauer gleich bleiben (z. B. ein Etikett mit dem Benutzernamen), ist es möglicherweise sinnvoll, das Ganze nur einmal mit Quartz zu zeichnen Knopfleiste usw. als Teil des Hintergrunds. In der Regel ist dies jedoch eine Optimierung, die nicht erforderlich ist, es sei denn, die Instruments-App weist Sie darauf hin.

51
uliwitness

Ich werde versuchen, eine Zusammenfassung dessen, was ich aus den Antworten anderer extrapoliere, zu erstellen und in einem Update der ursprünglichen Frage klärende Fragen zu stellen. Aber ich ermutige andere, weiterhin Antworten zu geben und diejenigen abzustimmen, die gute Informationen geliefert haben.

Allgemeiner Ansatz

Es ist ziemlich klar, dass der allgemeine Ansatz, wie Ben Sandofsky in seine Antwort erwähnte, lauten sollte: "Wann immer Sie können, verwenden Sie UIKit. Führen Sie die einfachste Implementierung durch, die funktioniert. Profil Wenn es einen Anreiz gibt, optimieren Sie. "

Das Warum

  1. Es gibt zwei mögliche Engpässe in einem iDevice: die CPU und die GPU
  2. Die CPU ist für das anfängliche Zeichnen/Rendern einer Ansicht verantwortlich
  3. Die GPU ist für die meisten Animationen (Core-Animationen), Ebeneneffekte, Compositing usw. verantwortlich.
  4. UIView verfügt über eine Vielzahl von Optimierungen, Zwischenspeichern usw., die für die Verarbeitung komplexer Ansichtshierarchien integriert sind
  5. Wenn Sie drawRect überschreiben, werden viele der Vorteile, die UIView bietet, nicht berücksichtigt. Im Allgemeinen ist dies langsamer, als wenn Sie UIView das Rendern überlassen.

Das Zeichnen von Zelleninhalten in einer einzigen UIView-Ebene kann die FPS beim Scrollen von Tabellen erheblich verbessern.

Wie ich bereits sagte, sind CPU und GPU zwei mögliche Engpässe. Da sie in der Regel unterschiedliche Aufgaben übernehmen, müssen Sie darauf achten, auf welchen Engpass Sie stoßen. Beim Scrollen von Tabellen wird Core Graphics nicht schneller gezeichnet, und aus diesem Grund können Sie Ihre FPS erheblich verbessern.

Tatsächlich ist Core Graphics möglicherweise langsamer als eine verschachtelte UIView-Hierarchie für das anfängliche Rendern. Es scheint jedoch, dass der typische Grund für abgehacktes Scrollen darin besteht, dass Sie einen Engpass bei der GPU haben. Deshalb müssen Sie sich damit befassen.

Warum das Überschreiben von drawRect (mithilfe von Kerngrafiken) das Scrollen in der Tabelle erleichtern kann:

Soweit ich weiß, ist die GPU nicht für das anfängliche Rendern der Ansichten verantwortlich, sondern übergibt Texturen oder Bitmaps, manchmal mit einigen Ebeneneigenschaften, nachdem sie gerendert wurden. Es ist dann für das Zusammenstellen der Bitmaps, das Rendern aller dieser Ebeneneffekte und den Großteil der Animation (Kernanimation) verantwortlich.

Bei Tabellenansichtszellen kann die GPU mit komplexen Ansichtshierarchien einen Engpass aufweisen, da statt einer einzelnen Bitmap die übergeordnete Ansicht animiert wird und Unteransichtslayoutberechnungen durchgeführt, Ebeneneffekte gerendert und alle Unteransichten zusammengesetzt werden. Anstatt eine Bitmap zu animieren, ist sie für die Beziehung zwischen mehreren Bitmaps und deren Interaktion für denselben Pixelbereich verantwortlich.

Zusammenfassend lässt sich sagen, dass das Zeichnen Ihrer Zelle in einer Ansicht mit Kerngrafiken das Scrollen in der Tabelle NICHT dadurch beschleunigen kann, dass sie schneller gezeichnet wird, sondern dass die GPU entlastet wird der Engpass, der Ihnen in diesem speziellen Szenario Probleme bereitet.

26
Bob Spryn

Ich bin ein Spieleentwickler und stellte die gleichen Fragen, als mein Freund mir sagte, dass meine UIImageView-basierte Ansichtshierarchie mein Spiel verlangsamen und es schrecklich machen würde. Anschließend recherchierte ich alles, was ich über die Verwendung von UIViews, CoreGraphics, OpenGL oder Drittanbietern wie Cocos2D herausfinden konnte. Die beständige Antwort, die ich von Freunden, Lehrern und Apple Ingenieuren bei WWDC erhielt, war, dass es am Ende keinen großen Unterschied geben wird, weil auf einer bestimmten Ebene tun sie alle dasselbe). Übergeordnete Optionen wie UIViews basieren auf den untergeordneten Optionen wie CoreGraphics und OpenGL. Sie sind lediglich in Code eingebettet, um die Verwendung zu vereinfachen.

Verwenden Sie CoreGraphics nicht, wenn Sie die UIView nur neu schreiben möchten. Mit CoreGraphics können Sie jedoch etwas schneller arbeiten, solange Sie alle Zeichnungen in einer Ansicht ausführen. Aber ist es wirklich wert? Die Antwort, die ich gefunden habe, ist normalerweise nein. Als ich mein Spiel zum ersten Mal startete, arbeitete ich mit dem iPhone 3G. Als mein Spiel an Komplexität zunahm, bemerkte ich einige Verzögerungen, aber mit den neueren Geräten war dies völlig unmerklich. Jetzt ist viel los und die einzige Verzögerung scheint ein Rückgang von 1-3 fps zu sein, wenn ich auf einem iPhone 4 auf dem komplexesten Level spiele.

Trotzdem habe ich mich entschieden, Instrumente zu verwenden, um die Funktionen zu finden, die die meiste Zeit in Anspruch nahmen. Ich stellte fest, dass die Probleme nicht mit meiner Verwendung von UIViews zu tun hatten. Stattdessen wurde CGRectMake wiederholt aufgerufen, um bestimmte Berechnungen zur Kollisionserkennung durchzuführen und Bild- und Audiodateien für bestimmte Klassen, die dieselben Bilder verwenden, separat zu laden, anstatt sie von einer zentralen Speicherklasse zeichnen zu lassen.

Am Ende ist es also möglich, mit CoreGraphics einen leichten Gewinn zu erzielen, aber normalerweise lohnt es sich nicht oder hat möglicherweise überhaupt keine Wirkung. Das einzige Mal, dass ich CoreGraphics verwende, ist das Zeichnen von geometrischen Formen anstelle von Text und Bildern.

6
WolfLink