Technologische Neuigkeiten, Bewertungen und Tipps!

Der App-Lebenszyklus von SwiftUI erklärt

Hinweis: Der folgende Artikel hilft Ihnen weiter: Der App-Lebenszyklus von SwiftUI erklärt

Zurück zum Blog

Tschüss AppDelegate! Sie können jetzt SwiftUI-Apps mit dem neuen App-Protokoll und Lebenszyklus erstellen, ohne einen App- oder Szenendelegierten zu benötigen. Wie funktioniert der Lebenszyklus der SwiftUI-App? Und wie konfiguriert man es? Lass es uns herausfinden!

In diesem Tutorial zur App-Entwicklung besprechen wir Folgendes:

  • Wie der SwiftUI-App-Lebenszyklus und Ihre App-Struktur funktionieren
  • Arbeiten mit @main, Szenen und Lebenszyklusereignissen
  • Bootstrapping der Benutzeroberflächen Ihrer App mit dem App-Protokoll
  • Arbeiten mit UIApplicationDelegateAdaptor auf die „alte Art“

Bevor wir zur App-Struktur kommen, gehen wir noch etwas zurück.

Jede App und jedes Computerprogramm hat einen Ausgangspunkt. Betrachten Sie es als die erste Funktion Ihrer App, die vom Betriebssystem (wie iOS) aufgerufen wird. Auf den meisten Plattformen heißt diese Funktion main().

Vor iOS 14 hatten die meisten iOS-Apps eine AppDelegate-Klasse (und optional eine SceneDelegate) in ihrem Xcode-Projekt. Dieser App-Delegat ist mit dem Schlüsselwort @UIApplicationMain versehen, was angibt, dass dieser App-Delegat der Ausgangspunkt der App ist.

Technisch gesehen erstellt iOS eine Instanz von UIApplication und weist der Delegat-Eigenschaft eine Instanz Ihrer App-Delegatenklasse zu. Aufgrund der Delegation können Sie jetzt anpassen, was beim Start der App passiert, sich in Lebenszyklusereignisse einbinden und „Routen“ konfigurieren, von denen aus Ihre App gestartet wird, wie z. B. benutzerdefinierte URLs und lokale/Remote-Benachrichtigungen.

Die wichtigste Funktion eines App-Delegaten ist application(_:didFinishLaunchingWithOptions:). Ironischerweise gibt diese Funktion fast immer true zurück. Es handelt sich um die „Hauptfunktion“ einer iOS-App, die erste, die während der Lebensdauer der App aufgerufen wird.

Sie verwenden es normalerweise für:

  • Erstkonfiguration von Komponenten und Frameworks von Drittanbietern, z. B. Einrichten von Firebase, Bugsnag, Realm, OneSignal, Parse Server, Reachability-Diensten usw
  • Wenn Sie keinen Szenendelegaten verwenden, richten Sie den Root-View-Controller des App-Fensters hier ein – die „erste“ Benutzeroberfläche der App (andernfalls würden Sie dies im Szenendelegierten oder in Info.plist tun).
  • Ändern Sie das globale Erscheinungsbild Ihrer App mithilfe von Erscheinungsbild-Proxys
  • Reagieren Sie auf eingehende Remote- oder lokale Benachrichtigungen, d. h. es geht eine Push-Benachrichtigung ein und Sie öffnen eine bestimmte Benutzeroberfläche in der App, im Gegensatz zur üblichen/Hauptbenutzeroberfläche

Der App Delegate wird auch zum Einbinden in Lebenszyklusereignisse verwendet. Das ist genau das, was das Wort „Lebenszyklus“ impliziert: Die App startet, wird aktiv, der Benutzer führt eine Aktion aus, und eine Weile später wird die App minimiert, in den Hintergrund verschoben und schließlich beendet.

Abhängig von Ihrer App sind Lebenszyklen überhaupt nicht wichtig – oder sehr wichtig. Beispielsweise muss ein iOS-Spiel pausieren, wenn Sie einen Anruf erhalten, und die App wird für eine Weile inaktiv, bis Sie das Spiel fortsetzen. Ressourcenintensive Apps müssen Timer und/oder Hintergrundprozesse anhalten oder sogar einen Hintergrundprozess starten, wenn Sie die App schließen. Das alles passiert im App Delegate!

Aber… was ist mit dem Lebenszyklus der SwiftUI-App?

Mit iOS 14 wurde das App-Protokoll eingeführt, das von vielen als „SwiftUI 2.0“ bezeichnet wird. Im Wesentlichen ersetzt das App-Protokoll den App-Delegaten und übernimmt viele seiner Funktionen. Ihre App ist jetzt eine „reine“ SwiftUI-App; Es gibt diesen App Delegate nicht mehr.

So sieht das für eine einfache App aus:

@hauptsächlich
struct BooksApp: App
{
Var-Körper: einige Szene {
Fenstergruppe {
Bücherliste()
}
}
}

Eindrucksvoll! Ziemlich prägnant, oder? Der obige Code bootet Ihre gesamte App und richtet sie mit einer ersten Ansicht namens BookList ein.

Folgendes ist los:

  • Das für die BooksApp-Struktur deklarierte @main-Attribut gibt an, dass diese Struktur „den Einstiegspunkt der obersten Ebene für den Programmablauf enthält“. Das ist eine schicke Art zu sagen, dass das App-Protokoll eine Standardimplementierung für eine statische Funktion main() hat, die die App beim Aufruf initialisiert.
  • Der Code struct BooksApp: App { deklariert eine Struktur namens BooksApp, die das Protokoll App übernimmt. Sie können dieser Struktur einen beliebigen Namen geben, aber es ist üblich, den Namen Ihrer App plus „~App“ zu wählen, z. B. BooksApp.
  • Der var body: some Scene {-Code deklariert die erforderliche Body-Eigenschaft für das App-Protokoll, die der Body-Eigenschaft einer SwiftUI-Ansicht unglaublich ähnlich ist. Sein Typ ist Scene (nicht View!), und dank des Schlüsselworts some hängt der konkrete Typ des Körpers von seiner Implementierung ab. Dies ist ein Boilerplate-Code. Bedenken Sie einfach, dass „eine App einen Körper hat, der eine (erste) Szene für die App bereitstellt“.
  • Innerhalb der Body-Eigenschaft befindet sich eine WindowGroup-Instanz. Dies ist eine plattformübergreifende Struktur, die eine Szene aus mehreren Elementen darstellt windows. Sie können es unter macOS, iOS usw. verwenden. Es ist der Container für die Ansichtshierarchie Ihrer Apps, ein bisschen wie das gute alte UIWindow.
  • Innerhalb der WindowGroup deklarieren Sie die erste Ansicht („Benutzeroberfläche“) für Ihre App. Im obigen Code ist das eine BookList-Ansicht – aber es kann natürlich alles sein.

Sie können nicht umhin, die Ähnlichkeiten zwischen dem SwiftUI View-Protokoll und dem App-Protokoll zu bemerken. Die Arbeit mit einer SwiftUI-App fühlt sich vertraut an!

Tatsächlich haben Sie möglicherweise bemerkt, dass die Präferenz von UIKit für die Delegation durch die Präferenz von SwiftUI für Zusammensetzbarkeit, Protokolle und Standardprotokollimplementierungen ersetzt wurde. Es ist dasselbe, aber anders.

Wie fängt man mit einer SwiftUI-App an? Erstellen Sie ein neues App-Projekt in Xcode und wählen Sie im Einrichtungsassistenten „SwiftUI App for Lifecycle“ aus. Das ist es!

Es gibt keinen einheitlichen Namen für die SwiftUI-App. Wie Sie gleich sehen werden, handelt es sich aus Code-Sicht um eine Struktur, die dem neuen App-Protokoll entspricht. In Xcode wird es oft als SwiftUI-App oder SwiftUI-App-Lebenszyklus bezeichnet. In diesem Tutorial werden wir es mit seinen vielen Namen bezeichnen – die meisten jedoch wahrscheinlich als „SwiftUI App“ (Großbuchstabe „A“).

Eine der Aufgaben einer SwiftUI-App und zuvor des App-Delegierten ist die Konfiguration Ihrer App. Sie möchten die Umgebung der App so einrichten, dass sie so funktioniert, wie sie soll. Beispielsweise indem die App auf einen Core Data-Kontext verweist.

Schauen Sie sich das hier an:

@hauptsächlich
struct CardsApp: App
{
let persistenceController = PersistenceController.preview

Var-Körper: einige Szene {
Fenstergruppe {
CardList()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}

Der obige Code verwendet einen Boilerplate-PersistenceController (Vorlage von Xcode), um eine Instanz von NSManagedObjectContext in die CardList-Ansicht einzufügen. Es ist ein gängiger Ansatz, einer SwiftUI-Ansicht Zugriff auf einen Core Data-Container zu gewähren, aber das geht über dieses Beispiel hinaus.

Im obigen Code sehen Sie, dass zwei wichtige Dinge passieren:

  1. Die CardsApp-Struktur verfügt über eine Eigenschaft persistenceController, die mit einem Standardwert initialisiert wird. Wenn die Struktur initialisiert wird, ist diese Eigenschaft ebenfalls vorhanden.
  2. Die Eigenschaft wird verwendet, um mit dem Modifikator „environment(_:_:)“ einen Wert in die Umgebung der CardList-Ansicht (und der Hierarchie) einzufügen.

In diesem Beispiel müssen der Core Data-Container (und der Persistenz-Controller) initialisiert werden, bevor die CardList-Ansicht auf dem Bildschirm angezeigt wird. Aus diesem Grund wird die Eigenschaft zur CardsApp-Struktur hinzugefügt. Wenn eine Instanz dieser Struktur initialisiert wird, wird auch der Persistenz-Controller initialisiert – gerade rechtzeitig bevor die Benutzeroberfläche auf dem Bildschirm angezeigt wird.

Hier ist ein weiteres Beispiel:

@hauptsächlich
struct BooksApp: App {

drin() {
Bugsnag.start()
}

Var-Körper: einige Szene {

}
}

Im obigen Code initialisieren wir den Bugsnag-Absturzmeldedienst. Dies geschieht in der Initialisierungsfunktion init() der App-Struktur. Bei der Initialisierung der App wird die Funktion Bugsnag.start() aufgerufen, die die Integration mit dem Bugsnag-Dienst weiter einrichtet.

Nach wie vor ist das genaue Beispiel nicht so wichtig – wohl aber der Ansatz, mit dem Abhängigkeiten eingerichtet werden. Wenn Ihr Ziel darin besteht, die App für die Ausführung vorzubereiten, ist init() ein großartiger Ort zum Einrichten von Diensten, Konfigurationsoptionen usw.

Nachdem Sie Ihre App nun eingerichtet haben, möchten Sie auf Lebenszyklusänderungen reagieren. Wir haben sie bereits besprochen: Was passiert beispielsweise, wenn Ihre App vom Benutzer minimiert wird?

Schauen Sie sich das an:

@hauptsächlich
struct BooksApp: App {
@Environment(\.scenePhase) private Variable scenePhase

Var-Körper: einige Szene {
Fenstergruppe {
Bücherliste()
}
.onChange(of: scenePhase) { Phase in
if Phase == .background {
// Ressourcen bereinigen, Timer stoppen usw.
}
}
}
}

Im obigen Code verwenden wir einige Kernprinzipien von SwiftUI. Kurz gesagt, der Modifikator onChange(of:perform:) ruft seinen Abschluss jedes Mal auf, wenn sich die „Phase“ von scenePhase ändert. Diese Szenenphase ist ein Umgebungswert, der sowohl für App- als auch für View-Instanzen verfügbar ist.

Die ScenePhase-Enumeration hat drei Zustände:

  1. aktiv – die Szene ist im Vordergrund und aktiv
  2. inaktiv – die Szene ist im Vordergrund, aber inaktiv
  3. Hintergrund – die Szene ist derzeit nicht in der Benutzeroberfläche sichtbar

Der Wechsel von einer Phase zur nächsten ist ein Lebenszyklusereignis. Es verrät Ihnen etwas darüber, was im „Leben“ einer App vor sich geht. Beispielsweise ändert sich die Phase Ihrer App in „Inaktiv“, wenn Sie den App Switcher aufrufen.

Stellen Sie sich vor, wir führen den folgenden Code aus. Probieren Sie es einfach selbst aus!

.onChange(of: scenePhase) { Phase in
drucken(Phase)
}

Dieser Code druckt effektiv jeden Szenenphasenwechsel für die App aus. Folgendes würden Sie finden:

  • Starten der App: aktiv
  • Aufrufen des App Switcher: aktiv zu inaktiv
  • Minimieren oder Schließen der App: Hintergrund
  • Öffnen der App über den App Switcher: inaktiv zu aktiv

Hierbei ist zu beachten, dass iOS die Kontrolle darüber hat, welcher Phasenwechsel wann erfolgt. Sie können sich nicht darauf verlassen, dass eine App zu einem bestimmten Zeitpunkt aktiv ist und dann immer die inaktive Phase durchläuft, bevor sie in den Hintergrund wechselt und die App beendet wird. Verlassen Sie sich nicht auf eine genaue Reihenfolge der Phasen.

Verwenden Sie stattdessen die Szenenphasen und Lebenszyklusereignisse, um auf eine für Ihre App geeignete Weise zu reagieren. Ein paar Beispiele:

  • Stoppen Sie ein Spiel oder eine intensive Berechnung, wenn die Phase inaktiv ist
  • Brechen Sie die Timer ab, wenn die Phase inaktiv ist, und starten Sie sie erneut, wenn sie aktiv ist
  • Stoppen Sie Netzwerkanfragen und schließen Sie geöffnete Dateien für den Hintergrund

Zu guter Letzt: Sie können Szenenphasen sowohl in einer Ansicht als auch für eine App beobachten. Innerhalb der App-Struktur erhalten Sie Aktualisierungen für jede Szene, die mit der App verbunden ist. Innerhalb einer Ansicht erhalten Sie nur Aktualisierungen für die Szene, zu der die Ansicht gehört. Das ist tatsächlich hilfreich, da Sie beispielsweise einen Timer „tief“ in der App abbrechen können, direkt in der Ansicht, die diesen Timer verwendet.

Note: Die Benennung der „Hintergrund“-Phase ist schwierig. Stellen Sie sich das so vor: „Diese App wird gleich beendet“, im Gegensatz zu „Diese App läuft im Hintergrund“. Wie wir bereits besprochen haben, hat iOS die Kontrolle über diese Phasen. Es kann durchaus sein, dass Ihre App scheinbar im Hintergrund läuft, weil sie noch im App Switcher sichtbar ist, während die App tatsächlich beendet ist, weil der Benutzer sie längere Zeit nicht verwendet hat.

Sind Sie noch nicht bereit, Ihren geliebten App Delegate loszulassen? Keine Sorge, Sie können es immer noch in einer SwiftUI-App über den @UIApplicationDelegateAdaptor-Eigenschaftswrapper verwenden!

Schauen Sie sich das an:

@hauptsächlich
struct ParseApp: App
{
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

Var-Körper: einige Szene {
Fenstergruppe {
ContentView()
}
}
}

Klasse AppDelegate: NSObject, UIApplicationDelegate
{
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = Null) -> Bool
{
ParseSwift.initialize(applicationId: „“, serverURL: URL(string: „http://localhost:1337/parse“)!)

Rückkehr wahr
}
}

ACH NEIN! Es ist ein App-Delegierter!?

Im obigen Code haben wir die App-Struktur im Wesentlichen angewiesen, eine Instanz von AppDelegate zu initialisieren, sie der appDelegate-Eigenschaft zuzuweisen und dieses Objekt als App-Delegat unserer App zu betrachten. All dies geschieht über den @UIApplicationDelegateAdaptor-Eigenschaftswrapper.

Sie sehen, dass die Klasse AppDelegate NSObject und UIApplicationDelegate entspricht, genau wie ein gewöhnlicher App-Delegat. Dies bedeutet auch, dass Sie jede verfügbare Delegate-Funktion verwenden können, z. B. application(_:didFinishLaunchingWithOptions:).

Sie können den UIApplicationDelegateAdaptor für alle Funktionen verwenden, die noch nicht im App-Protokoll verfügbar sind, z. B. Remote-Push-Benachrichtigungen. Das funktioniert vorerst genauso wie immer.

An dieser Stelle ist zu beachten, dass Sie den App-Delegaten zwar für alles verwenden können, es jedoch sinnvoll ist, ihn nur für Funktionen zu verwenden, die Sie mit der App-Struktur allein nicht erreichen können. Im obigen Beispiel für Parse Server könnten wir genauso gut Folgendes tun:

@hauptsächlich
struct ParseApp: App
{
drin() {
ParseSwift.initialize()
}

Var-Körper: eine Szene
}

Dies erspart uns eine ganze Menge Boilerplate-Code, was einer der wichtigsten Vorteile der Verwendung des SwiftUI-App-Lebenszyklus ist!

Die Struktur und der Lebenszyklus der SwiftUI-App sind interessant, oder? Es ist der Beginn eines neuen Ansatzes zum Bootstrapping und Konfigurieren „reiner“ SwiftUI-Apps. Auch wenn die App-Struktur nicht alle Funktionen unterstützt, über die die App- und Szenendelegierten traditionell verfügen, ist sie eine großartige Möglichkeit, eine einfache SwiftUI-App einzurichten.

Folgendes haben wir besprochen:

  • Wie richtet man eine SwiftUI-App und eine App-Struktur ein?
  • Arbeiten mit @main, Szenen und Lebenszyklusereignissen
  • Konfigurieren der Umgebung und Dienste der App
  • Verwendung von @UIApplicationDelegateAdaptor als Fallback-App-Delegat

Table of Contents