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
- Beschreibung und Berechnung des Layouts
1.1. Automatisches Layout
1.2. Manuelle frame
- 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 - Wie funktioniert der VKontakte-Feed?
- 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 - 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 .
, ( ). , : , .
, 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
— . , , .
, — . , .
, :
- Apple .
- Auto Layout .
- The Cassowary Linear Arithmetic Constraint Solving Algorithm .
- iOS Core Animation: Advanced Techniques. Nick Lockwood.
- WWDC 2014 Session 419. Advanced Graphics and Animations for iOS Apps.
