Komplexe Anzeigesammlungen in iOS: Probleme und Lösungen am Beispiel des VKontakte-Feeds

Hallo! Mein Name ist Sasha, ich bin ein iOS-Entwickler im Team, das den VKontakte-Feed erstellt. Jetzt werde ich Ihnen erklären, wie wir die Anzeige der Benutzeroberfläche optimieren und die damit verbundenen Probleme umgehen.
Ich denke du kannst dir vorstellen was VK Tape ist. Auf diesem Bildschirm können Sie verschiedene Inhalte anzeigen: Texte, statische Bilder, animierte Gifs, eingebettete Elemente (Video und Musik). All dies sollte reibungslos dargestellt werden, daher die hohen Anforderungen an die Leistung von Lösungen.


Lassen Sie uns nun sehen, welche Standardansätze für die Arbeit mit Zuordnungen existieren und welche Einschränkungen oder Vorteile berücksichtigt werden sollten.


Wenn Sie mehr zuhören als lesen möchten, finden Sie hier die Videoaufzeichnung des Berichts.



Inhalt


  1. Beschreibung und Berechnung des Layouts
    1.1. Automatisches Layout
    1.2. Manuelle frame
  2. Berechnung der Textgröße
    2.1. Standardmethoden zur Berechnung der Größe von UILabel / UITextView / UITextField
    2.2. NSAttributedString / NSString Methoden
    2.3. Textkit
    2.4. Coretext
  3. Wie funktioniert der VKontakte-Feed?
  4. So erzielen Sie eine bessere Leistung
    4.1 Warum Leistungsprobleme?
    4.2. CATransaction.commit
    4.3. Rendering-Pipeline
    4.4. Die verwundbarsten Orte für die Leistung
  5. Messwerkzeuge
    5.1. Metallsystemspur
    5.2. Wir beheben Leistungsverluste im Code, während die Anwendung ausgeführt wird

  • Wie man Probleme erforscht. Empfehlungen
  • Fazit
  • Informationsquellen

1. Beschreibung und Berechnung des Layouts


Erinnern wir uns zunächst daran, wie Sie mit normalen Tools eine visuelle Oberflächenstruktur ( Layout ) erstellen. Um Platz zu sparen, verzichten wir auf Listings - ich liste einfach die Lösungen auf und erkläre deren Eigenschaften.


1.1. Automatisches Layout


Die wahrscheinlich beliebteste Methode zum Erstellen einer Benutzeroberfläche unter iOS ist die Verwendung des Apple-Layoutsystems für das automatische Layout . Es basiert auf dem Cassowary- Algorithmus, der untrennbar mit dem Konzept der Einschränkungen verbunden ist.


Denken Sie vorerst daran, dass die mit Auto Layout implementierte Benutzeroberfläche Einschränkungen unterliegt.


Merkmale des Ansatzes:


  • Das Beschränkungssystem wird in ein lineares Programmierproblem umgewandelt .
  • Cassowary löst das resultierende Optimierungsproblem mit der Simplex-Methode . Diese Methode hat eine exponentielle asymptotische Komplexität. Was bedeutet das? Wenn die Anzahl der Einschränkungen im Layout zunimmt, können sich die Berechnungen im schlimmsten Fall exponentiell verlangsamen.
  • Die resultierenden frame Werte für UIView sind die Lösung für das entsprechende Optimierungsproblem.

Vorteile der Verwendung von Auto Layout:


  • Bei einfachen Abbildungen ist ein linearer Rechenaufwand möglich .
  • Es verträgt sich gut mit allen Standardelementen, da es sich um die "native" Technologie von Apple handelt.
  • Out of the Box funktioniert mit UIView .
  • Verfügbar in Interface Builder, mit dem Sie das Layout in einem Storyboard oder XIB beschreiben können.
  • Es garantiert eine aktuelle Lösung auch während des Übergangs. Dies bedeutet, dass der frame Wert jeder UIView immer (!) Eine Lösung für die eigentliche Layout-Aufgabe ist.

Die Systemfunktionen sind für die meisten Displays ausreichend. Es ist jedoch nicht zum Erstellen von Bändern mit einer großen Menge heterogener Inhalte geeignet. Warum?


Es ist wichtig, sich daran zu erinnern, dass Auto Layout:


  • Funktioniert nur im Hauptthread . Angenommen, Apple-Ingenieure haben den Mainstream als Synchronisationspunkt für die Auto-Layout-Lösung und die Frame-Werte aller UIView . Ohne dies müssten Sie das automatische Layout in einem separaten Thread berechnen und die Werte ständig mit dem Haupt-Thread synchronisieren.
  • Es kann langsam bei komplexen Darstellungen arbeiten , da es auf einem Brute-Force-Algorithmus basiert, dessen Komplexität im schlimmsten Fall exponentiell ist.
  • Verfügbar mit iOS 6.0 . Nun ist dies kein Problem, aber es lohnt sich, darüber nachzudenken.

Fazit: Mit AutoLayout können Anzeigen ohne oder mit Sammlungen, aber ohne komplexe Beziehungen zwischen Elementen erstellt werden.


1.2. Manuelle frame


Die Essenz des Ansatzes: Wir berechnen alle frame selbst. Beispielsweise implementieren wir die Methoden layoutSubviews , sizeThatFits . Das heißt, in layoutSubviews ordnen layoutSubviews alle sizeThatFits Elemente selbst an, in sizeThatFits berechnen wir die Größe entsprechend der gewünschten Position der sizeThatFits Elemente und des Inhalts.


Was gibt es? Wir können komplexe Berechnungen in den Hintergrund-Stream übertragen, und im Haupt-Stream können relativ einfache Berechnungen durchgeführt werden.


Was ist das Problem? Sie müssen die Berechnungen selbst durchführen, es ist leicht, einen Fehler zu machen. Sie müssen auch sicherstellen, dass die Position der Kinder und die in sizeThatFits Ergebnisse sizeThatFits .


Selbsteinschätzung ist gerechtfertigt, wenn:


  • Wir sind auf Leistungseinschränkungen für das automatische Layout gestoßen oder gehen davon aus, dass diese auftreten werden.
  • Die Anwendung verfügt über eine komplexe Sammlung, und es besteht eine gute Chance, dass das entwickelte Element in eine seiner Zellen fällt.
  • Wir möchten die Größe des Elements im Hintergrund-Thread berechnen.
  • Auf dem Bildschirm werden nicht standardmäßige Elemente angezeigt, deren Größe je nach Inhalt oder Umgebung ständig neu berechnet werden muss.

Ein Beispiel. Zeichnen Sie QuickInfos, die automatisch an den Inhalt angepasst werden. Der interessanteste Teil dieser Aufgabe besteht darin, die visuelle Größe des Texts in den einzelnen QuickInfos zu berechnen.




2. Berechnung der Textgröße


Dieses Problem kann auf mindestens vier Arten gelöst werden, von denen jede auf eigenen Methoden beruht. Und jeder hat seine eigenen Merkmale und Grenzen.


2.1. Standardmethoden zur Berechnung der Größe von UILabel / UITextView / UITextField


Die sizeThatFits (standardmäßig in sizeToFit ) und intrinsicContentSize (in Auto Layout verwendet) geben die bevorzugte Größe des Ansichtsinhalts zurück. Mit ihrer Hilfe können wir beispielsweise herausfinden, wie viel Platz der in UILabel geschriebene UILabel .


Der Nachteil ist, dass beide Methoden nur im Haupt-Thread funktionieren - sie können nicht vom Hintergrund aus aufgerufen werden.


Wann sind Standardmethoden sinnvoll?


  • Wenn wir bereits sizeToFit oder Auto Layout verwenden.
  • Wenn es Standardelemente in der Anzeige gibt und wir ihre Größe im Code erhalten möchten.
  • Für alle Displays ohne komplexe Sammlungen.

2.2. NSAttributedString / NSString-Methoden


Beachten Sie die sizeWithAttributes boundingRect und sizeWithAttributes . Ich UILabel davon ab, sie zum Lesen der Größe des Inhalts von UILabel / UITextView / UITextField . Ich habe nirgendwo in der Dokumentation Informationen gefunden, dass die NSString Methoden und die Layout-Methoden der UIView Elemente auf demselben Code (denselben Klassen) basieren. Diese beiden Klassengruppen gehören unterschiedlichen Frameworks an: Foundation bzw. UIKit. Vielleicht mussten Sie das boundingRect-Ergebnis bereits an die UILabel Größe UILabel ? Oder sind Sie auf die Tatsache NSString , dass NSString die Emoji-Größe nicht berücksichtigen ? Dies sind die Probleme, die Sie bekommen können.


Ich erkläre Ihnen auch, welche Klassen für das Zeichnen von Text in UILabel / UITextView / UITextField , aber kehren UITextField zu den Methoden zurück.


Die Verwendung von boundingRect und sizeWithAttributes lohnt sich, wenn wir:


  • Wir zeichnen nicht standardmäßige Schnittstellenelemente mit drawInRect , drawAtPoint oder anderen Methoden der NSAttributedString NSString / NSAttributedString .
  • Wir möchten die Größe der Elemente im Hintergrund-Stream berücksichtigen. Auch dies ist nur bei Verwendung der entsprechenden Rendering-Methoden möglich.
  • Zeichnen Sie in einem beliebigen Kontext , und zeigen Sie beispielsweise eine Linie über dem Bild an.

2.3. Textkit


Dieses Tool besteht aus den Standardklassen NLayoutManager , NSTextStorage und NSTextContainer . Layout UILabel / UITextView / UITextField ebenfalls auf ihnen.


TextKit ist sehr praktisch, wenn Sie die Position des Texts detailliert beschreiben und angeben müssen, um welche Formen er fließen soll :



Mit TextKit können Sie die Größe der Oberflächenelemente in der Hintergrundwarteschlange sowie die frame Zeilen / Zeichen berechnen. Darüber hinaus können Sie mit dem Framework Glyphen zeichnen und das Erscheinungsbild des Texts innerhalb des vorhandenen Layouts vollständig ändern. All dies funktioniert in iOS 7.0 und höher.


TextKit ist nützlich, wenn Sie:


  • Text mit komplexem Layout anzeigen;
  • zeichne Text auf Bilder;
  • Berechnen Sie die Größe der einzelnen Teilzeichenfolgen.
  • Zählen Sie die Anzahl der Zeilen.
  • Verwenden Sie die Ergebnisse von Berechnungen in einer UITextView .

Ich betone noch einmal. Wenn Sie die Größe der UITextView berechnen müssen, konfigurieren wir zuerst die Instanzen der NSTextContainer NSLayoutManager , NSTextStorage und NSTextContainer und übergeben diese Instanzen an die entsprechende UITextView , wo sie für das Layout verantwortlich sind. Nur so garantieren wir die volle Übereinstimmung aller Werte.


Verwenden Sie TextKit nicht mit UILabel und UITextField ! Für sie (im Gegensatz zu UITextView ) können Sie NSLayoutManager , NSTextStorage und NSTextContainer nicht konfigurieren.


2.4. Coretext


Dies ist das unterste Textwerkzeug in iOS. Es bietet maximale Kontrolle über das Rendern von Schriftarten, Zeichen, Linien und Einzügen. Und genau wie mit TextKit können Sie typografische Parameter des Texts berechnen, z. B. die Grundlinie und die Rahmengröße einzelner Zeilen.


Wie Sie wissen, ist die Verantwortung umso höher, je mehr Freiheit Sie haben. Und um mit CoreText gute Ergebnisse zu erzielen, müssen Sie in der Lage sein, seine Methoden zu verwenden.


CoreText bietet Threadsicherheit für Operationen an den meisten Objekten. Dies bedeutet, dass wir seine Methoden aus verschiedenen Threads aufrufen können. Zum Vergleich: Wenn Sie TextKit verwenden, müssen Sie selbst über die Reihenfolge der Methodenaufrufe nachdenken.


CoreText sollte verwendet werden, wenn:


  • Für den direkten Zugriff auf Textparameter wird eine extrem einfache Low-Level-API benötigt. Ich muss sofort sagen, dass für die meisten Aufgaben die Fähigkeiten von TextKit ausreichen.
  • Es gibt viel zu tun mit einzelnen Zeilen ( CTLine ) und Zeichen / Elementen.
  • Unterstützung ist in iOS 6.0 wichtig.

Für den VKontakte-Feed haben wir CoreText verwendet. Warum? Zu der Zeit, als wir die Grundfunktionen für das Arbeiten mit Text implementiert haben, war TextKit noch nicht da.




3. Wie funktioniert der VKontakte-Feed?


Kurz darüber, wie wir Daten vom Server erhalten, Formularlayout und Anzeigen.



Betrachten Sie zunächst die in der Hintergrundwarteschlange ausgeführten Aufgaben. Wir empfangen Daten vom Server, verarbeiten sie und beschreiben die nachfolgende Anzeige deklarativ. Zu diesem Zeitpunkt haben wir noch keine UIView Instanzen, wir legen nur die Regeln und die Struktur der zukünftigen Schnittstelle mit unserem deklarativen Tool fest, ähnlich wie bei SwiftUI . Zur Berechnung des Layouts berechnen wir den gesamten frame unter Berücksichtigung der aktuellen Einschränkungen, z. B. der Breite des Bildschirms. Wir aktualisieren die aktuelle dataSource ( dataSourceUpdate ). Hier in der Hintergrund-Warteschlange bereiten wir die Bilder vor: Führen Sie eine Dekomprimierung durch (siehe Abschnitt Leistung für weitere Details), zeichnen Sie Schatten, Runden und andere Effekte.


Gehen Sie nun zur Hauptwarteschlange. dataSourceUpdate empfangene dataSourceUpdate auf UITableView , verwenden und verarbeiten die Schnittstellenereignisse und füllen die Zellen.


Um unser Layoutsystem zu beschreiben, wäre ein separater Artikel erforderlich, aber hier werde ich seine Hauptmerkmale auflisten:


  • Eine deklarative API ist ein Satz von Regeln, auf denen eine Schnittstelle basiert.
  • Grundkomponenten bilden einen Baum ( nodes ).
  • Einfache Berechnungen in Grundkomponenten. In Listen berechnen wir beispielsweise nur den Nullpunktversatz unter Berücksichtigung der Breite / Höhe aller untergeordneten Elemente.
  • Basiselemente erzeugen keine unnötigen „Container“ des UIView in der Hierarchie. Beispielsweise bildet die UIView keine zusätzliche UIView und fügt ihr keine UIView hinzu. Stattdessen berechnen wir den origin untergeordneten Elemente relativ zum übergeordneten Element (für das Listenelement).
  • Low-Level-Textverwaltung mit CoreText.

Aber selbst mit diesem Ansatz ist die Anzeige von Bändern aufgrund von Leistungsproblemen möglicherweise nicht reibungslos. Warum?


Jede Zelle hat eine komplexe Hierarchie von nodes . Und obwohl die Grundelemente keine unnötigen Container erstellen, wird in der UIView noch viel UIView angezeigt. Und wenn Sie die Hierarchie mit "Knoten" (Ansichtsbindung) in der Hauptwarteschlange füllen, entsteht zusätzliche Arbeit, die nur schwer zu umgehen ist.


Wir haben versucht, so viele Aufgaben wie möglich in die Hintergrundwarteschlange zu übertragen und fahren jetzt damit fort. Darüber hinaus gibt es CPU- und GPU-intensive Vorgänge, die berücksichtigt und umgangen werden müssen.




4. So erzielen Sie eine bessere Leistung


Die einfachste Antwort ist, den Hauptthread, die CPU und die GPU auszulagern. Dazu müssen Sie die Arbeit von iOS-Anwendungen genau verstehen. Identifizieren Sie vor allem die Ursachen von Problemen.


4.1 Warum Leistungsprobleme?


Core Animation, RunLoop und Scroll
Erinnern wir uns, wie die Schnittstelle in iOS aufgebaut ist. Auf der obersten Ebene befindet sich UIKit , das für die Interaktion mit dem Benutzer verantwortlich ist: Umgang mit Gesten, Aufwecken der Anwendung aus dem Ruhezustand und ähnliche Dinge. Für das Rendern der Oberfläche ist ein untergeordnetes Tool zuständig - Core Animation (wie in macOS). Dies ist ein Framework mit einem eigenen Schnittstellenbeschreibungssystem . Betrachten Sie die grundlegenden Konzepte zum Erstellen einer Schnittstelle.


Bei der CALayer besteht die gesamte Oberfläche aus CALayer Ebenen. Sie bilden einen Render-Baum, der über CATransaction Transaktionen verwaltet wird.


Eine Transaktion ist eine Gruppe von Änderungen, genauer gesagt Informationen über die Notwendigkeit, etwas in der angezeigten Oberfläche zu aktualisieren. Jede Änderung an frame oder anderen Layer-Parametern fällt in die aktuelle Transaktion. Falls noch nicht geschehen, erstellt das System selbst eine implizite Transaktion .


Mehrere Transaktionen bilden einen Stapel. Neue Aktualisierungen fallen in die oberste Transaktion des Stapels.


Jetzt wissen wir, dass wir zum Aktualisieren des Bildschirms Transaktionen mit neuen Parametern für den Ebenenbaum erstellen müssen.



Wann und wie werden Transaktionen erstellt? In unserer Anwendung haben Threads eine Entität namens RunLoop . In einfachen Worten, dies ist eine Endlosschleife, bei der bei jeder Iteration die aktuelle Warteschlange von Ereignissen verarbeitet wird.


Im Hauptthread wird RunLoop benötigt, um Ereignisse aus verschiedenen Quellen zu verarbeiten, z. B. eine Schnittstelle (Gesten), Zeitgeber oder Handler zum Empfangen von Daten von NSStream und NSPort .



Wie hängen Core Animation und RunLoop ? Wir haben oben festgestellt, dass das System beim Ändern der Eigenschaften einer Ebene im Render-Baum bei Bedarf implizite Transaktionen erstellt (daher müssen wir CATransaction.begin nicht aufrufen. CATransaction.begin , um etwas neu zu zeichnen). Außerdem schließt RunLoop System bei jeder RunLoop Iteration offene Transaktionen automatisch und wendet die vorgenommenen Änderungen an ( CATransaction.commit ).


Beachten Sie! Die Anzahl der RunLoop Iterationen hängt nicht von der Aktualisierungsrate des Bildschirms ab. Der Zyklus ist überhaupt nicht mit dem Bildschirm synchronisiert und funktioniert wie "endless while() ".


Nun wollen wir sehen, was in den RunLoop Iterationen im Haupt-Thread während des RunLoop passiert:


  ... if (dispatchBlocks.count > 0) { //   MainQueue doBlocks() } ... if (hasPanEvent) { handlePan() // UIScrollView change content offset -> change bounds } ... if (hasCATransaction) { CATransaction.commit() } ... 

Zunächst werden Blöcke ausgeführt, die über dispatch_async / dispatch_sync zur Hauptwarteschlange hinzugefügt wurden. Und bis sie abgeschlossen sind, fährt das Programm nicht mit den folgenden Aufgaben fort.


Als Nächstes beginnt UIKit, die Pan-Geste des Benutzers zu verarbeiten. Im Rahmen der Verarbeitung dieser Geste ändert sich UIScrollView.contentOffset und infolgedessen UIScrollView.bounds . Durch Ändern der bounds UIScrollView (bzw. ihrer Nachkommen UITableView , UICollectionView ) wird der sichtbare Teil des Inhalts ( viewport ) aktualisiert.


Am Ende der RunLoop Iteration wird bei offenen Transaktionen automatisch ein commit oder ein flush .


Um zu überprüfen, wie dies funktioniert, setzen Sie Haltepunkte an die entsprechenden Stellen.
So sieht die Gestenverarbeitung aus:



Und hier ist CATransaction.commit nach handlePan :



Während der UIScrollView erstellt UIScrollView einen CADisplayLink Timer, um die Anzahl der Änderungen an contentOffset pro Sekunde mit der Aktualisierungsrate des Bildschirms zu synchronisieren.



Wir stellen CATransaction.commit dass CATransaction.commit nicht am Ende der RunLoop Iteration, sondern direkt in der Verarbeitung des CADisplayLink Timers CADisplayLink . Aber das spielt keine Rolle:



4.2. CATransaction.commit


Tatsächlich werden alle Vorgänge in CATransaction.commit auf CALayer Ebenen ausgeführt. layoutSublayers haben ihre eigenen Methoden zum Aktualisieren des Layouts ( layoutSublayers ) und des Bilds ( drawLayer ). Die Standardimplementierung dieser Methoden führt dazu, dass Methodenaufrufe delegiert werden. Indem UIView der UIView Hierarchie eine neue Instanz von UIView , fügen Sie implizit die entsprechende Ebene zur Hierarchie der Core Animation-Ebene hinzu. In diesem Fall ist UIView standardmäßig ein Delegat seiner Ebene. Wie Sie aus dem Aufrufstapel UIView führt UIView im Rahmen der Implementierung der CALayer seine Methoden aus. CALayer wird erläutert:



Da wir normalerweise mit der UIView Hierarchie arbeiten, wird die Beschreibung mit Beispielen für UIView .


Während CATransaction.commit wird das Layout aller mit UIView markierten setNeedsLayout . Beachten Sie, dass wir selbst layoutSubviews oder layoutIfNeeded aufgrund ihrer garantierten verzögerten Ausführung im System in CATransaction.commit . Selbst wenn Sie in einer Transaktion (zwischen den Aufrufen von CATransaction.begin und CATransaction.commit ) den frame mehrmals CATransaction.begin und CATransaction.commit setNeedsLayout , wird jede Änderung nicht sofort setNeedsLayout . Letzte Änderungen werden erst nach dem Aufruf von CATransaction.commit . Relevante CALayer Methoden: setNeedsLayout , layoutIfNeeded und layoutSublayers .


Eine ähnliche Gruppe zum Zeichnen bilden die Methoden setNeedsDisplay und drawRect . Für CALayer dies setNeedsDisplay , displayIfNeeded und drawLayer . CATransaction.commit ruft die Rendering-Methoden für alle mit setNeedsDisplay gekennzeichneten setNeedsDisplay . Dieser Schritt wird manchmal als Offscreen-Zeichnen bezeichnet.


Ein Beispiel . Nehmen Sie zur UITableView und Vereinfachung die UITableView :


  ... // Layout UITableView.layoutSubviews() //  ,   .. ... // Offscreen drawing UITableView.drawRect() //    ... 

UIKit verwendet UITableView / UICollectionView in layoutSubviews : willDisplayCell die willDisplayCell und so weiter. CATransaction.commit Drawing erfolgt während CATransaction.commit : Es werden die drawInContext Methoden aller Layer oder der drawRect aller UIView , die setNeedsDisplay . Ich merke, wenn wir in drawRect etwas drawRect , geschieht dies auf dem Haupt-Thread, und wir müssen dringend die Anzeige der Ebenen für einen neuen Frame ändern. Es ist klar, dass eine solche Lösung sehr ineffizient sein kann.


Was passiert als nächstes in CATransaction.commit ? Der Render Tree wird an den Render Server gesendet.


4.3. Rendering-Pipeline


Erinnern Sie sich an den gesamten Prozess der Bildung eines Schnittstellenrahmens in iOS (Rendering-Pipeline [WWDC 2014-Sitzung 419. Erweiterte Grafiken und Animationen für iOS-Apps]):



Nicht nur der Prozess unserer Anwendung ist für die Bildung des Frames verantwortlich - Core Animation arbeitet auch in einem separaten Systemprozess namens Render Server.


Wie der Rahmen entsteht. Wir (oder das System für uns) erstellen eine neue Transaktion ( CATransaction ) in der Anwendung mit einer Beschreibung der Schnittstellenänderungen, CATransaction sie fest und übertragen sie an den Render-Server. Auf der Anwendungsseite ist alles erledigt. Anschließend dekodiert der Render-Server die Transaktion (Render Tree), ruft die erforderlichen Befehle auf dem Videochip auf, zeichnet einen neuen Frame und zeigt ihn auf dem Bildschirm an.


Interessanterweise wird beim Erstellen des Frames ein bestimmtes „Multithreading“ verwendet. Wenn die Bildwiederholfrequenz 60 Bilder pro Sekunde beträgt, wird insgesamt kein neues Bild in 1/60, sondern in 1/30 Sekunde erstellt. Dies liegt daran, dass der Render-Server, während die Anwendung einen neuen Frame vorbereitet, noch den vorherigen verarbeitet:



Grob gesagt beträgt die Gesamtzeit für die Erstellung des Frames vor der Anzeige auf dem Bildschirm in unserem Prozess für die Erstellung der Transaktion 1/60 Sekunde und im Render-Server-Prozess während der Transaktionsverarbeitung 1/60 Sekunde.


Ich möchte die folgende Bemerkung machen. Wir können das Zeichnen von Ebenen selbst parallelisieren und den Inhalt der UIImage / CGImage im Hintergrundstream rendern . Anschließend müssen Sie im Haupt-Thread das erstellte Image der Eigenschaft CALayer.contents . In Bezug auf die Leistung ist dies ein sehr guter Ansatz. Es sind die Entwickler, die es Texture verwenden . Da wir CALayer.contents nur CALayer.contents des Erzeugens einer Transaktion CALayer.contents des Erzeugens unserer Anwendung ändern können, haben wir nur 1/60 Sekunde bei 60 Bildern, um ein neues Bild zu erstellen und zu ersetzen, anstatt 1/30 Sekunde (unter Berücksichtigung von Optimierungen und Parallelisierung der Rendering-Pipeline mit dem Rendering-Server) )


Darüber hinaus kann der Render-Server weiterhin das Mischen (siehe unten) und das kurzfristige Zwischenspeichern von Ebenen verarbeiten [iOS Core Animation: Advanced Techniques. Nick Lockwood]. 1/60 CALayer.contents , . .


: , .


4.4.


Main-thread



1. ( CATransaction.commit ) - UIView.layoutSubviews UIView (, CALayer ). , layoutSubviews / cellForRow / willDisplayCell .


2. drawInContext / drawRect . - Main- ( CATransaction.commit ) — . , .


3. . . CATransaction.commit , , .


4. . UIImage / CGImage .


5. . Main-thread , scroll. - , UI.


6. Main-. , RunLoop Main- , , Main-. .


GPU



Blending . GPU ( Render Server GPU, ). , , Background-.


. , UIBlurEffect , UIVibrancyEffect , , (Render Pass). , , .


Offscreen rendering (Render Server)



Render Server . , , :



CALayer , , Offscreen rendering. , UIVisualEffect ( , Render Server CPU, GPU).


, .




5.


, , Time Profiler. Metal System Trace — Time Profiler .


5.1. Metal System Trace


, ( ). , : , .


, Metal System Trace , . , Render Server. , Main-, — , .



- , :



Metal System Trace . 64- , iPhone 5s. , . , - , , UI.


5.2.


. , - - . , CADisplayLink .


CADisplayLink timestamp — ( Render Server). CADisplayLink.timestamp timestamp . , (, 1/60 ) :


  //  CADisplayLink. link = [CADisplayLink displayLinkWithTarget:target selector:selector] [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode] //    CADisplayLink : diff = prevTimestamp - link.timestamp if (diff > 1/fps) { //  freeze } prevTimestamp = link.timestamp 

CADisplayLink UITrackingRunLoopMode , .


Rendering Pipeline:


UI-, . «» freezeFrameTimeRate :


 scrollTime //    Scroll freezeFrameTime //    ,  "",       freezeFrameTimeRate = freezeFrameTime / scrollTime 

, - UIView . , «»:



, , « UIView » . Warum? , . , , , : CADisplayLink , Render Server link.timetamp , Render Server , . 60 UI-, Render Server. Render Server , .


, , , Render Server . Metal , Render Server. , , iOS, Render Server .


. Empfehlungen


, , . , .


: — ! — .




Fazit


— . , , .





, — . , .


, :


  1. Apple .
  2. Auto Layout .
  3. The Cassowary Linear Arithmetic Constraint Solving Algorithm .
  4. iOS Core Animation: Advanced Techniques. Nick Lockwood.
  5. WWDC 2014 Session 419. Advanced Graphics and Animations for iOS Apps.

Source: https://habr.com/ru/post/de481626/


All Articles