
Manchmal befinden Sie sich in einer Situation, in der Ihre App keine gute Leistung erbringt. Hier sind einige Instrumente, die Sie verwenden können, und Best Practices, die Sie implementieren können, um die Dinge zu verbessern.
Dies ist der zweite Teil des Artikels, der auf dem Keynote-Vortrag von Luke Parham, einem iOS-Ingenieur bei Fyusion und Autor von Tutorials für die iOS-Entwicklung auf RayWenderlich.com, auf der International Mobile Developers Conference
MBLT DEV im Jahr 2017
basiert .
Kernanimationsinstrument
Wenn Sie viele Profile erstellt und alle Engpässe festgestellt haben, treten manchmal immer noch Leistungsprobleme auf. Dies liegt an der Funktionsweise der Benutzeroberfläche in iOS. Jedes Mal, wenn Sie Frames festlegen oder UIViews erstellen, geschieht tatsächlich unter der Haube eine CATransaction, oder das System erstellt sie für Sie. Und diese werden an einen Gegenstand namens "Render-Server" verschickt. Der Rendering-Server ist für die Erstellung von Animationen verantwortlich. Wenn Sie ein UIView animateWith ausführen: Was auch immer, dies geschieht alles auf dem Render-Server, der ein weiterer Thread ist und alle Animationen der App verarbeitet.

Hier ist ein Zeitprofiler, der oben eine Bildratenanzeige hat. Und unten finden Sie die wichtigsten Debug-Optionen. Wir werden über die zwei wichtigsten und am einfachsten zu behebenden Probleme berichten.

Die erste ist farbgemischte Schichten. Dies ist wirklich einfach zu beheben. Und das bringt uns zum ersten Abschnitt der Leistungspolizei. Grundsätzlich haben viele Apps Probleme: Selbst iMessage, die beliebte Apple-App, macht eine Menge nicht wirklich großartiger Dinge. Hier sehen wir, dass es viel Rot gibt:

Rot bedeutet, dass Sie Etiketten mit weißem Hintergrund haben. Und dann befinden sie sich auf einem anderen weißen Hintergrund und sind aus irgendeinem Grund nicht so eingestellt, dass sie undurchsichtig sind. Der Mixer mischt also diese Farben, Weiß und Weiß, und erhält dadurch eine weiße Farbe. Für jedes Pixel mit Rot werden zusätzliche Berechnungen ohne Nutzen durchgeführt. Im Hintergrund wird immer noch Weiß angezeigt.
Um dies zu vermeiden, können Sie Ebenen nach Möglichkeit undurchsichtig machen, wenn sie dieselbe Farbe und dieselbe Farbe haben. Wenn die Unteransicht dieselbe Hintergrundfarbe hat, ist eine Überblendung nicht erforderlich. Alles, was Sie tun müssen, ist, die Deckkraft Ihrer Ebenen auf 1 zu setzen und dann sicherzustellen, dass die Hintergrundfarbe eingestellt ist. Wenn Ihre Hintergrundfarbe klar ist, ist sie immer nicht undurchsichtig.

Offscreen-Rendering
Offscreen-gerenderte Elemente werden gelb angezeigt, wenn Sie diese Option aktivieren. Das Schöne am Core Animation-Instrument ist, dass Sie andere Apps sehen können. Sie können diese Optionen aktivieren und dann zu einer beliebigen App in Ihrem System wechseln, um zu sehen, was sie falsch machen. In diesem Fall hat Instagram oben diese kleinen Blasen, die Ihnen die Geschichten der Leute zeigen.

Wie Sie sehen können, sind sie alle gelb. Auf dem iPhone 5 sind sie aggressiv langsam. Dies liegt daran, dass das Rendern außerhalb des Bildschirms viel schlechter ist als das Mischen von Alpha. Es blockiert die GPU. Am Ende müssen zusätzliche Berechnungen zwischen der GPU und der CPU durchgeführt werden, sodass Sie zusätzliche Verzögerungen erhalten, die die meiste Zeit nicht erforderlich sind.
Bezier-Pfad statt Kurvenfahrt
Die nächste Regel: Verwenden Sie nicht die Eigenschaft Eckenradius. Wenn Sie eine Ansicht haben und view.layer.cornerRadius festlegen, wird immer ein Off-Screen-Rendering eingeführt. Stattdessen können Sie einen Bezier-Pfad und die gleiche Art von CGBitmap-Material von früher verwenden. In diesem Fall ein UIGraphics-Kontext. Diese Funktion arbeitet mit UIImage, das eine Größe annimmt, abgerundete Ecken basierend auf dieser Größe erstellt und einen Bezierpfad zum Abschneiden verwendet. Dann schneiden wir das Bild ab und geben es aus dem UIImage-Kontext zurück. Dies gibt also ein vorgerundetes Bild zurück, anstatt die Ansicht zu runden, in der sich das Bild befindet.

Das letzte Beispiel. Hier ist Twitter und dies ist eine Echtzeitansicht dieser laufenden Animation. Es soll sich öffnen und Ihnen die Informationen anzeigen, aber all dieser Text und das gesamte Material wurden außerhalb des Bildschirms gerendert, sodass die Animation auf ein Crawlen verlangsamt wurde. Dies ist die am wenigsten leistungsfähige Sache, die ich jemals in einer App im App Store gefunden habe.

Wie ist das passiert? Eine Sache, die dazu führt, ist die shouldRasterize-Eigenschaft eines CALayer. Dies ist eine Option auf einer Ebene, mit der Sie gerenderte Texturen zwischenspeichern können. Es gibt viele seltsame Regeln. Wenn es in einer bestimmten Anzahl von Millisekunden nicht verwendet wurde, verlässt es den Cache. Wenn es dann den Cache verlässt, wird es in jedem Frame außerhalb des Bildschirms gerendert. Es ist die möglichen Vorteile, die es hat, nicht wirklich wert. Und es ist schwer zu überprüfen, ob es Ihnen tatsächlich zugute kommt.
Zusammenfassung
Vermeiden Sie das Rendern außerhalb des Bildschirms und das Mischen von Alpha, wenn Sie können. Alpha-Blending ist manchmal notwendig. Es ist besser als das Rendern außerhalb des Bildschirms. Das Rendern außerhalb des Bildschirms erfolgt aus mehreren Gründen. Es kann aus Schatten geschehen; es kann durch Eckenrundung passieren; es kann durch Maskieren passieren.
Machen Sie Ansichten nach Möglichkeit undurchsichtig. Verwenden Sie die Eigenschaft "Eckenradius" nicht so oft wie möglich mit Bezier-Pfaden. Verwenden Sie die Ebenenschatteneigenschaften auch nicht, wenn Sie Textschatten ausführen. Sie können stattdessen NSShadow verwenden.
Aktivitätsspur
Die Aktivitätsverfolgung ist eine Art viel niedrigere Version von etwas, das der Zeitprofiler tun würde. Sie erhalten einen Überblick über alle Ihre Threads und deren Interaktion. Und es ist ziemlich kompliziert. Aber es hat wirklich schöne Funktionen, die Sie einrichten können.
Systemablaufverfolgung
Verwenden Sie die
Systemverfolgung , um die Zeiten für bestimmte Ereignisse zu verfolgen. Sie können Möglichkeiten einrichten, um bestimmte Ereignisse und Codeabschnitte zu verfolgen und zu sehen, wie lange sie in einer realen Anwendung dauern. Auf diese Weise erhalten Sie detaillierte Informationen zu den Vorgängen in Ihrem System.
- Verwenden Sie "Wegweiser", um zu signalisieren, wenn etwas Wichtiges passiert.
- Punkte sind einzelne Ereignisse, wenn / wenn Sie sehen möchten, wie eine Animation aufgetreten ist oder so etwas.
- Regionen haben einen Anfang und ein Ende. Bei der Bilddecodierung können Sie sehen, wann sie beginnt und wann sie endet, sodass Sie abschätzen können, wie lange es im Allgemeinen gedauert hat.

So richten Sie eine Systemablaufverfolgungsvorlage ein. Sie erstellen diese Liste von Ereignissen, die auftreten können. Nummer eins ist also ein Bilddownload. Zwei ist eine Bilddecodierung, und drei ist diese Neigungsanimation, die ich hinzugefügt habe. Grundsätzlich richten Sie einige zusätzliche Optionen ein, um zu sehen, welche Farben angezeigt werden. Grundsätzlich senden Sie ihm eine Nummer wie 1 oder 2, die je nach dem, was Sie dort einsenden, rot oder grün ist.

Wenn Sie sich in Objective-C befinden, müssen Sie diesen kdebug_signpost-Header importieren. In Swift ist es nur für Sie verfügbar.

Und dann müssen Sie diese Funktion aufrufen, entweder kdebug_signpost oder kdebug_signpost_start und kdebug_ signpost_end. Und sie arbeiten mit dem Code, den Sie übergeben haben. Also haben wir diese drei Ereignisse mit diesen Zahlen eingerichtet. Dann geben Sie diese Nummer hier ein. Sie übergeben ihm ein Objekt, das im Grunde der Schlüssel für dieses Ereignis ist. Und dann ist die letzte Zahl die Farbe. 2 ist also so, als wüsstest du rot oder so.
Ich habe ein Swift-Beispielprojekt auf
GitHub . Ich habe die Dinge irgendwie vereinfacht. Es gibt einen Anfang und ein Ende, die etwas einfacher zu handhaben sind.
So sieht es aus, wenn Sie eine Ablaufverfolgung ausgeführt haben. Es wird Ihnen zunächst nichts zeigen. Wenn Sie dann die App beenden, führt sie einige Berechnungen durch und zeigt Ihnen hier Informationen.

Hier können wir unsere Bilddownloads sehen, die ungefähr 200 Millisekunden gedauert haben. Und dann gibt es eine Bilddecodierung, die etwa 40 Millisekunden gedauert hat. Das ist wirklich cool, wenn du eine Menge verrückter Dinge in deiner App hast. Sie können alle diese Ereignisse einrichten und dann einfach anzeigen, wie lange sie jeweils dauern und wie sie miteinander interagieren. Das ist es für die Systemverfolgung.
Bonus
Schauen Sie sich das Beispiel einer Kameraverlangsamung an, bei der wir sehen können, was passiert, wenn die App AR-Elemente enthält:

Wir haben einen Effekt angewendet, der 26,4% aller Berechnungen für jeden Frame in Anspruch nahm, um nur einen Effekt zu berechnen. Und es verlangsamte die Kamera auf etwas Verrücktes wie 10 Bilder pro Sekunde.
Als ich mich hier vertiefte und mir diesen großen Engpass ansah, sah ich, dass das Wichtigste, was den größten Teil der Arbeit erledigte, die Verwendung intensiver NSDispatchData war.

Dies ist eine Unterklasse von NSData. Und das alles ist das Abrufen von Bytes mit Bereichsfunktion. Und das ist eine einfache Funktion. Es werden lediglich einige Bytes aus den Daten entnommen und an einer anderen Stelle abgelegt. Es ist nicht zu verrückt, aber anscheinend nahmen alle Dinge, die es intern tat, 18% dieser 26% ein.
Regel Nr. 1Es ist eine NSData und es bekommt Bytes. Das ist eine einfache Objective-C-Sache, aber wenn Sie darauf stoßen und das ein Engpass ist, ist es Zeit, stattdessen auf C umzusteigen. Da der Engpass bei einem Aufruf zum Abrufen von Float-Werten lag, können Sie einfach memcpy () verwenden. Mit memcpy () können Sie einen Datenblock an einen anderen Ort verschieben. Reduziert ziemlich viel Overhead.
Wenn Sie wie NSData suchen, sind diese Klassen wie Tausende von Zeilen. Da ist also viel los. In diesem Fall haben wir das Original in rot.

Hier erhalten Sie einen Bereich, nehmen einige Bytes und kopieren sie in den Puffer. Die memcpy () - Version ist fast genau dasselbe. Es sieht nicht komplizierter aus und macht aggressiv weniger Dinge.

Wenn wir das ändern und es erneut ausführen, stieg der Wert von 26% auf 0,6%, indem diese eine Zeile in memcpy () geändert wurde. Und dann stieg die Bildrate dramatisch an.
Regel Nr. 2Vermeiden Sie Überzeichnungen, wenn Sie eine Art Rendering-App ausführen oder wenn Sie so etwas wie eine Ladeleiste ausführen. Häufig treten Ereignisse mit mehr als 60 Bildern pro Sekunde auf. In diesem Fall können Sie dieses Update der Benutzeroberfläche mithilfe eines CADisplayLink drosseln. Es hat eine Eigenschaft namens PreferredFramesPerSecond. Das ist nur für iOS 10 oder höher. Für ältere müssen Sie es manuell tun, aber es ist immer noch nützlich.

Sie können die gewünschte Framerate einstellen. Viele Male für das Laden von Balken werde ich es auf ungefähr 15 Bilder pro Sekunde einstellen, weil es nicht wirklich wichtig ist. Es müssen nicht 60 Bilder pro Sekunde aktualisiert werden. Dies kann Ihnen viel Arbeit ersparen, wenn die Dinge so oder so gleich aussehen.
Regel Nr. 3Verwenden Sie IMP-Caching. Dies ist nur für Objective-C nützlich. Wenn Sie inObjective-C eine Methode unter der Haube aufrufen, rufen Sie tatsächlich die Objective-C-Funktion zum Senden von Nachrichten auf (objc_msgSend ()). Wenn Sie diese Anrufe in Spuren sehen, die einen großen Teil der Zeit in Anspruch nehmen, ist dies etwas, das Sie tatsächlich leicht loswerden können. Es ist im Grunde die Cache-Tabelle, in der Sie Funktionszeiger nachschlagen, indem Sie ihr einen Namen für eine Methode geben. Anstatt diese Suche jedes Mal durchzuführen, können Sie den Funktionszeiger zwischenspeichern und einfach direkt aufrufen. Normalerweise ist es mindestens doppelt so schnell.

Wenn Sie keinen zwischengespeicherten Zeiger haben, können Sie ihn durch Aufrufen von methodForSelector abrufen :. Dann rufen wir diese Methode einfach wie einen regulären Funktionsaufruf auf. Sie übergeben dem Objekt den Selektor und danach kommen alle Argumente.
Regel 4Verwenden Sie kein ARC. ARC ist etwas, das eine Menge Overhead hinzufügt. In Ihrem Code passiert all dieses Zeug und es ist alles mit Retains und Releases bestreut. Es macht so viel wie es muss, und es macht eine Menge mehr. Wenn Sie also wirklich optimieren möchten, wenn Sie feststellen, dass Ihre Ablaufverfolgung eine Reihe von Aufbewahrungs- und Freigabeanrufen enthält und diese eine Menge Zeit in Anspruch nehmen, können Sie einfach auf die Nichtverwendung von ARC umsteigen, was viel mehr Arbeit bedeutet.
Es ist auch schwierig, Ihre Teamkollegen dazu zu bringen, dem zuzustimmen und nicht sauer zu sein.
Verwenden Sie Swift nicht, wenn es besonders leistungsempfindlich ist. Swift ist eine schöne Sprache. Es hat einige wirklich nette Funktionen. Es wird jedoch auch mehr Boilerplate verwendet, um ein hohes Maß an Funktionalität zu erreichen. Wenn Sie schnell sein möchten, sollten Sie so nah wie möglich an die Baugruppe heranrücken. Und das geht schneller, weil es automatisch weniger Code gibt.
Wenn Sie sich mit dem Zeug beschäftigen, wenn Sie es für interessant hielten, gibt es ein wirklich gutes Buch mit dem Titel „iOS und MacOS: Performance Tuning“ von Marcel Weiher. Es geht sehr tief in viele dieser Sachen und viel mehr darüber hinaus. Ich habe auch eine Videoserie. Ich mache Videos für RayWenderlich. Es gibt eine praktische Instrumentenserie, die ich gemacht habe und die diese Dinge etwas ausführlicher erklärt und einige Beispiele enthält. Wenn Sie also mehr über Instrumente erfahren möchten, können Sie sich diese Videoserie ansehen. Und dann WWDC-Videos - es gibt eine Menge davon, die verschiedene Performance-Dinge wie diese erklären.
Video
Hier finden Sie den ersten Teil eines Artikels, der auf Lukes Vortrag basiert. Sehen Sie sich hier den vollständigen Vortrag an: