
Es gibt viele Tipps und Tricks, mit denen iOS-Entwickler wissen, wie Leistungsoptimierungen vorgenommen werden können, damit Animationen in Anwendungen reibungslos ausgeführt werden. Nachdem Sie den Artikel gelesen haben, werden Sie feststellen, was 16,67 Millisekunden für iOS-Entwickler bedeuten und welche Tools besser zum Aufspüren des Codes geeignet sind.
Der Artikel basiert auf dem Keynote-Vortrag von
Luke Parham , derzeit iOS-Ingenieur bei Apple und Autor von Tutorials für die iOS-Entwicklung auf RayWenderlich.com, auf der International Mobile Developers Conference
MBLT DEV 2017 .
„Hey Leute. Wenn Sie beispielsweise die Startzeit um 10 Sekunden verkürzen können, multiplizieren Sie diese mit 5 Millionen Benutzern und das sind 50 Millionen Sekunden pro Tag. Über ein Jahr sind das wahrscheinlich Dutzende von Leben. Wenn Sie es also zehn Sekunden schneller booten lassen, haben Sie ein Dutzend Leben gerettet. Das ist es wirklich wert, findest du nicht? "
Steve Jobs über die Leistung (Startzeit von Apple II).Leistung in iOS oder wie man aus der Hauptleitung kommt
Der Haupt-Thread ist dafür verantwortlich, Benutzereingaben zu akzeptieren und Ergebnisse auf dem Bildschirm anzuzeigen. Tippen, Schwenken, alle Gesten akzeptieren und dann rendern. Die meisten modernen Mobiltelefone rendern mit 60 Bildern pro Sekunde. Dies bedeutet, dass jeder die gesamte Arbeit innerhalb von 16,67 Millisekunden erledigen möchte. Das Verlassen des Hauptfadens ist also eine wirklich große Sache.
Wenn etwas länger als 16,67 Millisekunden dauert, werden automatisch Frames gelöscht, und Ihre Benutzer sehen es, wenn Animationen ausgeführt werden. Einige Geräte haben noch weniger Zeit zum Rendern, zum Beispiel hat das neue iPad 120 Hertz, sodass nur 8 Millisekunden pro Frame für die Arbeit zur Verfügung stehen.
Frames fallen gelassen
Regel 1: Verwenden Sie einen CADisplayLink, um abgelegte Frames zu verfolgen
CADisplayLink ist ein spezieller Timer, der auf dem Vsync ausgelöst wird. Vsync wird ausgeführt, wenn die App auf dem Bildschirm gerendert wird. Dies geschieht alle 16 Millisekunden. Zu Testzwecken können Sie in Ihrem AppDelegate CADisplayLink einrichten, das der Hauptlaufschleife hinzugefügt wurde, und dann einfach eine andere Funktion haben, in der Sie ein wenig rechnen. Dann verfolgen Sie, wie lange die App ausgeführt wurde und wie lange es her ist, seit diese Funktion das letzte Mal ausgelöst wurde. Und sehen Sie, ob es länger als 16 Millisekunden gedauert hat.

Dies wird nur ausgelöst, wenn es tatsächlich gerendert werden kann. Wenn Sie eine Menge Arbeit erledigt und den Hauptthread verlangsamt haben, wird dieser 100 Millisekunden später ausgeführt, was bedeutet, dass Sie zu viel Arbeit geleistet und in dieser Zeit Frames gelöscht haben.
Dies ist beispielsweise die App Catstagram. Es hat Ruckler, wenn das Bild geladen wird. Und dann können Sie sehen, dass der Frame zu einem bestimmten Zeitpunkt gelöscht wurde und eine verstrichene Zeit von etwa 200 Millisekunden hatte. Das bedeutet, dass diese App etwas tut, das zu lange dauert.

Benutzer mögen eine solche Erfahrung nicht, insbesondere wenn die App ältere Geräte wie iPhone 5, alte iPods usw. unterstützt.
Zeitprofiler
Time Profiler ist wahrscheinlich das nützlichste Tool, um das Zeug aufzuspüren. Die anderen Tools sind nützlich, aber letztendlich verwenden wir in Fyusion den Time Profiler in 90% der Fälle. Die üblichen Verdächtigen der Anwendung sind Bildlaufansicht, Text und Bilder.
Bilder sind die wirklich großen. Wir haben JPEG-Decodierung - "UIImageView" entspricht etwas UIImage. UIimages dekodieren alle JPEGs für die App. Sie tun es langsam, so dass Sie die Leistung nicht direkt verfolgen können. Es passiert nicht richtig, wenn Sie das Bild festlegen, aber Sie können es in Zeitprofiler-Traces sehen.
Textmessung ist eine weitere große Sache. Es zeigt sich, zB wenn Sie viele wirklich komplexe wie Japanisch oder Chinesisch haben. Die Messung von Linien kann lange dauern.
Das Hierarchie-Layout verlangsamt auch das App-Rendering. Dies gilt insbesondere für das automatische Layout. Es ist praktisch, aber im Vergleich zum manuellen Layout auch aggressiv langsam. Es ist also einer dieser Kompromisse. Wenn die App dadurch verlangsamt wird, ist es möglicherweise an der Zeit, von der App abzuweichen und eine andere Layouttechnik auszuprobieren.
Beispielverfolgung

Im Beispielaufrufbaum können Sie sehen, wie viel Arbeit Ihre CPUs leisten. Sie können die Ansichten wechseln, sie nach Threads und nach CPUs betrachten. Normalerweise ist es am interessantesten, nach Threads zu trennen und dann zu schauen, was auf main ist.
Wenn Sie sich das zum ersten Mal ansehen, scheint es oft sehr überwältigend zu sein. Sie haben manchmal das Gefühl: „Was ist das für ein Müll? Ich weiß nicht, was dies "FRunLoopDoSource0" bedeutet.
Aber es ist eines der Dinge, in die man sich vertiefen und verstehen kann, wie Dinge funktionieren, und es beginnt Sinn zu machen. Sie können also der Stapelverfolgung folgen und alle Systemelemente anzeigen, die Sie nicht geschrieben haben. Aber unten sehen Sie Ihren tatsächlichen Code.
Der Anrufbaum
Zum Beispiel haben wir eine wirklich einfache App, die die Hauptfunktion hat und dann einige Methoden innerhalb der Hauptfunktion aufruft. Der Zeitprofiler erstellt standardmäßig jede Millisekunde eine Momentaufnahme der aktuellen Stapelverfolgung. Dann wartet es eine Millisekunde und macht einen Schnappschuss, in dem Sie "main" genannt haben, was "foo" heißt, was "bar" heißt. Über dem Screenshot befindet sich die erste Stapelverfolgung. Das wird also gesammelt. Wir haben diese Zählungen: 1, 1, 1.

Jede dieser Funktionen wurde einmal aufgerufen. Dann, eine Millisekunde später, erfassen wir einen weiteren Stapel. Und diesmal ist es genau das Gleiche, wir erhöhen alle Zählungen um 2.

In der dritten Millisekunde haben wir dann einen etwas anderen Aufrufstapel. Main ruft direkt "bar" an. Main und Bar sind um eins. Aber dann haben wir eine Trennung. Manchmal rufen Hauptanrufe "foo", manchmal Hauptanrufe "bar" direkt. Das passiert einmal. Eine Methode wurde in einer anderen aufgerufen.
Weiterhin wurde eine Methode in einer anderen aufgerufen, die die dritte Methode aufruft. Wir sehen, dass "buz" zweimal aufgerufen wurde. Aber es ist eine so kleine Methode, dass sie zwischen einer Millisekunde stattfindet.
Bei Verwendung des Zeitprofilers ist zu beachten, dass die genauen Zeiten nicht angegeben werden. Es sagt nicht genau, wie lange eine Methode dauert. Es gibt an, wie oft es in Snapshots angezeigt wird. Dies kann nur annähern, wie lange die Ausführung der einzelnen Methoden gedauert hat. Denn wenn etwas kurz genug ist, wird es nie auftauchen.

Wenn Sie in der Anrufstruktur in den Konsolenmodus wechseln, können Sie alle Frame-Drop-Ereignisse sehen und abgleichen. Wir haben eine Menge Frames, die fallen gelassen werden, und wir haben eine Menge Arbeit im Gange. Sie können den Zeitprofiler vergrößern und sehen, was gerade in diesem Abschnitt ausgeführt wurde.

Tatsächlich können Sie auf einem Mac im Allgemeinen mit der Option auf Offenlegungsdreiecke klicken, und es wird auf magische Weise geöffnet und zeigt Ihnen, was dort am wichtigsten ist. Es wird auf das herunterfallen, was die meiste Arbeit erledigt. Und in 90% der Fälle handelt es sich um CFRunLoopRun und dann um die Rückrufe.

Die gesamte App basiert auf einer Run Loop. Sie haben diese Schleife, die für immer läuft, und dann werden bei jeder Iteration der Schleife die Rückrufe aufgerufen. Wenn Sie an diesem Punkt angelangt sind, können Sie einen Drilldown zu jedem dieser Punkte durchführen und sich im Grunde ansehen, was Ihre drei oder vier größten Engpässe sind.
Wenn wir uns mit einem dieser Dinge befassen, können wir solche Dinge sehen, bei denen es wirklich einfach ist, sie zu betrachten, und sagen: „Wow, ich weiß nicht, was das tut.“ Wie Renderings, Bildanbieter, IO.

Es gibt eine Option, mit der Sie Systembibliotheken ausblenden können. Es ist wirklich verlockend, sich zu verstecken, aber in Wirklichkeit ist dies der größte Engpass in der App.
Es gibt die Gewichte, die zeigen, wie viel Prozent der Arbeit diese bestimmte Funktion oder Methode leistet. Und wenn wir das Beispiel genauer untersuchen, haben wir 34% und das liegt an Apple jpeg_decode_image_all. Nach ein wenig Recherche wird klar, dass dies bedeutet, dass die JPEG-Decodierung im Haupt-Thread stattfindet und den größten Teil des Frames verliert.

Regel Nr. 2
Im Allgemeinen ist es besser, JPEGs im Hintergrund zu dekodieren. Die meisten Bibliotheken von Drittanbietern (AsyncDisplayKit, SDWebImage, ...) tun dies sofort. Wenn Sie keine Frameworks verwenden möchten, können Sie dies selbst tun. Sie übergeben ein Bild. In diesem Fall handelt es sich um eine Erweiterung von UIImage. Anschließend richten Sie einen Kontext ein und zeichnen das Bild manuell in einen Kontext in eine CGBitmap.

Wenn Sie dies tun, können Sie die decodierte Image () -Methode aus einem Hintergrundthread aufrufen. Dadurch wird immer das dekodierte Bild zurückgegeben. Es gibt keine Möglichkeit zu überprüfen, ob insbesondere UIImage bereits dekodiert ist, und Sie müssen sie hier immer weiterleiten. Wenn Sie die Dinge jedoch richtig zwischenspeichern, wird keine zusätzliche Arbeit geleistet.
Dies ist technisch weniger effizient. Die Verwendung von UIimageView ist sehr optimiert und sehr effizient. Es wird Hardware-Dekodierung durchführen, so dass es ein Kompromiss ist. Ihre Bilder werden auf diese Weise langsamer dekodiert. Aber das Gute ist, dass Sie in eine Hintergrundwarteschlange senden, Ihr Bild mit der gerade gesehenen Methode dekodieren und dann zurück zum Haupt-Thread springen und Ihren Inhalt festlegen können.

Obwohl diese Arbeit länger dauerte, geschah sie möglicherweise nicht im Hauptthread, sodass die Benutzerinteraktion nicht blockiert wurde, da das Scrollen nicht blockiert wurde. Das ist also ein Gewinn.
Speicherwarnungen
Jedes Zeichen, dass Sie eine Speicherwarnung erhalten, dass Sie alles löschen möchten, löschen Sie den gesamten nicht verwendeten Speicher, den Sie können. Wenn Sie jedoch Probleme mit Hintergrund-Threads haben, beansprucht das Zuweisen dieser großen dekodierten JPEGs viel neuen Speicher für Hintergrund-Threads.
Dies geschah in der Fyuse-App. Wenn ich zu einem Hintergrund-Thread springen und alle meine JPEGs dekodieren würde, in einigen Fällen wie bei älteren Telefonen, würde das System diese sofort beenden. Und das liegt daran, dass eine Erinnerungswarnung gesendet wird, die sagt: „Hey! Werde dein Gedächtnis los “, aber die Hintergrundwarteschlangen hören nicht zu. Was passiert, wenn Sie alle diese Bilder zuweisen und es dann jedes Mal abstürzt? Um dies zu umgehen, müssen Sie den Haupt-Thread vom Hintergrund-Thread aus anpingen.

Im Allgemeinen ist der Hauptthread eine Warteschlange. Dinge werden in die Warteschlange gestellt und passieren im Haupt-Thread. Wenn Sie in Objective-C zum Hintergrund wechseln, können Sie performSelectorOnMainThread: withObject: waitUntilDone: verwenden. Dadurch wird es an das Ende der Hauptwarteschlangenzeile gesetzt. Wenn die Hauptwarteschlange also mit der Verarbeitung von Speicherwarnungen beschäftigt ist, wird dieser Funktionsaufruf an das Ende der Zeile gesendet und wartet, bis alle Speicherwarnungen verarbeitet sind, bevor die gesamte Zuweisung von ausgeführt wird Speicher
In Swift ist es einfacher. Sie können einen leeren Hauptblock für den Versand synchron auf main ausführen.
Hier ist ein Beispiel, in dem wir aufgeräumt haben und Bilddecodierungen in Hintergrundwarteschlangen durchführen. Und das visuelle Scrollen ist viel schöner. Wir haben immer noch Frame Drops, aber dies ist auf einem iPod 5g, also ist es eines der schlimmsten Dinge, die Sie testen können, die immer noch iOS 10 und 11 unterstützen.

Wenn Sie diese Rahmentropfen haben, können Sie weiter suchen. Es ist noch Arbeit im Gange und verursacht diese Frame-Drops. Sie können noch mehr tun, um es schneller zu machen.
Zusammenfassend ist es nicht immer so einfach, aber wenn Sie kleine Dinge haben, die viel Zeit in Anspruch nehmen, können Sie sie im Hintergrund erledigen.
Stellen Sie sicher, dass es nicht mit UIKit zusammenhängt. Viele UIKit-Klassen sind nicht threadsicher und Sie können diese UIView nicht im Hintergrund zuordnen.
Verwenden Sie Core Graphics, wenn Sie Bildsachen im Hintergrund ausführen müssen. Verstecken Sie keine Systembibliotheken. Und vergessen Sie nicht die Speicherwarnungen.
Dies ist der erste Teil eines Artikels, der auf Luke Parhams Präsentation basiert. Wenn Sie mehr über die Funktionsweise der Benutzeroberfläche in iOS erfahren möchten, warum Sie einen Bezierpfad verwenden und wann Sie auf die manuelle Speicherverwaltung zurückgreifen müssen, lesen Sie den zweiten Teil eines Artikels
hier .
Video
Sehen Sie sich hier den vollständigen Vortrag an: