SwiftUI und Architektur: Zustand

19.01.2022
Libor Huspenina

​​​​In der mobilen Welt ist eine neue Ära angebrochen. Zumindest im Kontext von UI-Frameworks. Was bedeutet das für Ihr Projekt? Wird Ihre Architektur standhalten und eine schrittweise Einführung ermöglichen? Oder werden Sie Ihr Projekt komplett neu schreiben müssen?

Willkommen in der neuen alten Welt

Der Eckpfeiler der Entwicklung von Benutzeroberflächen für iOS ist seit den Anfängen UIKit (oder AppKit für macOS). Es bietet einen Mechanismus zum Definieren von Fenster- und Ansichtshierarchien, zum Navigieren zwischen Szenen, zum Rendern von Inhalten auf dem Bildschirm und verfügt über Tools für die Handhabung von Benutzerinteraktionen. UIKit wurde vor über einem Jahrzehnt in der Objective-C-Welt entwickelt und nutzt Prinzipien, die damals üblich waren, wie Delegaten und Vererbung.  Funktionale Elemente moderner Sprachen wie Swift führen natürlich zu einem deklarativen Programmierparadigma, und genau hier setzt SwiftUI an.

Das SwiftUI-Framework wurde auf der WWDC 2019 angekündigt und führt den oben erwähnten deklarativen Ansatz zum Schreiben von UI-Code ein, bevorzugt Komposition, verwendet Wertetypen in großem Umfang und bietet Optimierungen unter der Haube. Es ändert komplett die Art und Weise, wie wir über die Benutzeroberfläche nachdenken und wie wir den UI-Code schreiben. Die Prinzipien und das Design von SwiftUI sind von React aus der Javascript-Welt inspiriert, das auf das Jahr 2013 zurückgeht. Unser Dank und Applaus gebührt daher der JS-Community für die Ausarbeitung der Macken! 

In SwiftUI geschriebener Code ist plattformübergreifend und kann auf den Plattformen tvOS, macOS und watchOS bereitgestellt werden. Die Interoperabilität mit UIKit und AppKit erleichtert den Übergang noch weiter.

Interessanterweise wurde ein sehr ähnlicher Ansatz auch in der Android-Welt eingeführt, bekannt als Jetpack Compose. Wenn Sie sich mehr dafür interessieren, ist es in den Beiträgen „Jetpack Compose: Was Sie wissen müssen“, Teil 1 und Teil 2​, gut beschrieben, obwohl sich seitdem zwangsläufig einige Dinge geändert haben.

Beide Technologien sind für den produktiven Einsatz bereit. Konzentrieren wir uns darauf, was die Ankunft von SwiftUI aus architektonischer Sicht bedeutet, insbesondere in Bezug auf den Zustand. 

SwiftUI und Zustand

In SwiftUI ist eine Ansicht eine Funktion eines Zustands, nicht einer Folge von Ereignissen, die einen internen, gekapselten Zustand verändern. Was bedeutet das in der Praxis?

SwiftUI beobachtet den Zustand der Benutzeroberfläche und löst bei jeder Änderung ein neues Rendering der Ansicht aus (mit einigen intelligenten Optimierungen unter der Haube). Dies geschieht durch Property-Wrapper, die UI-Statusobjekte und/oder Combine-Publisher annotieren. Es liegt nur an Ihnen, den Zustand zu modellieren, zu halten, zu transformieren, zu komponieren und zu verteilen. 

Der Zustand kann aus vielen verschiedenen Blickwinkeln wahrgenommen werden, zum Beispiel: 

  • was auf dem Bildschirm gerendert wird, 

  • Daten, die im Speicher gehalten werden,

  • Zustände, die über mehrere Bildschirme oder Funktionen verteilt sind. 

Es ist von entscheidender Bedeutung, diese Arten von Zuständen korrekt voneinander zu trennen, da einige eine begrenzte Rolle in einem lediglich gekapselten Kontext spielen, während andere sich über mehrere Kontexte erstrecken und als Quelle der Wahrheit dienen können.

Eine solche Quelle der Wahrheit muss eindeutig definiert und gemeinsam genutzt werden. Werfen wir einen Blick darauf, wie die derzeit meist diskutierten Architekturen dies angehen.

MVx-Architekturen

MVC wurde früher am häufigsten in iOS-Anwendungen verwendet. Mit dem Aufkommen der funktionalen reaktiven Programmierung geriet es ins Abseits und wurde durch MVVM und dessen Derivat MVVM-C ersetzt, das eine zusätzliche Schicht für die Navigation vorsieht. Der Platz für View- und Präsentationslogik ist in diesen Fällen klar, aber die Modellschicht hat zu viele Verantwortlichkeiten, was zu großen Komponenten führen kann, die zu viele Belange haben. In einem anderen Fall kann die Modellschicht nur Domänenobjekte enthalten, während die Präsentationsschicht sich mit der Vernetzung, der Speicherung, der Geschäftslogik und der Umwandlung von Domänenobjekten in renderbare Objekte für den View auf der einen Seite sowie der Verarbeitung von Aktionen des Views auf der anderen Seite befasst. Riesige ViewController mit vielen Verantwortlichkeiten führten zu dem Scherz, dass MVC die Abkürzung für Massive-View-Controller ist.

MVx-Architekturen sind ihrer Definition nach nicht ausreichend, um das gesamte System zu entwerfen. Sie sind lediglich ein Muster für den Präsentationsteil des Systems. Saubere und robuste Anwendungen können mit MVVM als einzigem Architekturwerkzeug in Ihrem Werkzeugkasten geschrieben werden, aber es erfordert strenge Disziplin und eine angemessene Dekomposition.  Die verwendete Architektur verzweigt sich oft in etwas Anspruchsvolleres als eine 3-Schichten-Architektur, aber ohne klare Grenzen, die irgendwo zwischen den Zeilen definiert sind.

Redux

Da die SwiftUI von React aus der Javascript-Welt inspiriert ist, wo React und Redux oft Hand in Hand gehen, liegt es nahe, darüber nachzudenken, wie sich dies in der Entwicklung mobiler Anwendungen widerspiegelt.

Redux ist ein Mechanismus zum Aktualisieren und Verteilen von Zuständen. Es kann nicht als „Architektur“ betrachtet werden, da es keine Antworten auf die wichtigen Fragen gibt.​

  • Wie kann die Anwendung vor der Außenwelt geschützt werden?

  • Wie lassen sich Technologien und Abhängigkeiten von Drittanbietern einfach austauschen?

  • Wie definiert man strikte Grenzen zur Isolierung von Funktionen? 

  • Wo sind die Geschäftsregeln definiert?

  • Welche Teile befassen sich mit der Navigation, der Präsentation, der Vernetzung, der Speicherung, der Benutzereingabe und so weiter?

Diese diskutierten Architekturen gehen nicht vollständig auf die Modellierung und die gemeinsame Nutzung von Zuständen ein. Sie werden oft entweder auf eine schlechte Art und Weise verwendet, die nicht sehr lesbar und nachhaltig ist, oder auf eine Art und Weise, die von Natur aus zu einer komplexeren Architektur mit einer lockeren Definition der Verantwortlichkeiten führt.

Obwohl SwiftUI ein neuer Spieler auf dem mobilen Spielplatz ist, ist die Verwaltung von Zuständen definitiv nicht neu.  Wir werden sehen, ob die alten und traditionellen Prinzipien in dieser neuen Welt noch etwas zu bieten haben.

Lektionen aus der Geschichte

Es gibt viele Muster und Prinzipien, die zeitlos sind, nämlich zwei, die es ermöglichen, den Zustand auf transparente Weise zu speichern und zu verteilen.

Repository-Muster

Ein Repository ist ein Entwurfsmuster, das die Datenzugriffslogik von der Geschäftszugriffslogik abstrahiert, indem es eine sammlungsartige Schnittstelle für den Zugriff auf Geschäftseinheiten bereitstellt. Die eigentliche Implementierung der Schnittstelle kann mit der Datenbank, der REST-API und anderen Anforderungen Ihres Projekts kommunizieren, solange ihre Details nicht an die Schnittstelle der Geschäftsdomäne weitergegeben werden.

Ein solches Repository könnte leicht die Quelle der Wahrheit für jede Funktion sein. Repositories halten natürlich den Status, aber sehen wir uns an, wie man denselben Status auf mehrere Teile der Anwendung verteilt.

Injektion von Abhängigkeiten

Die gemeinsame Nutzung des Zustands von Funktionen ist so einfach wie die gemeinsame Nutzung derselben Repository-Instanz.  Frameworks für Dependency Injection helfen bei der Organisation des Abhängigkeitsgraphen. Einige verwenden Service-Locator-Muster, die die Instanzauflösung hinter einer Abstraktionsschicht abstrahieren. Dadurch wird der Boilerplate-Code reduziert, der sonst bei manueller Dependency Injection erforderlich wäre. Die Auflösung kann zu Laufzeitabstürzen führen, wenn einige angeforderte Instanzen nicht registriert sind. Das ist ein Nachteil, den man aber mit Unit-Tests in den Griff bekommen kann. 


Sie können ein gemeinsames Repository auch mit reiner Sprache ohne Abhängigkeits-Frameworks implementieren. Das Prinzip bleibt in beiden Fällen das gleiche.

Architektur und Zustand

Es ist wichtig, Ihr System so zu gestalten​

  • dass es weich und anpassungsfähig ist, damit Sie gewünschte Änderungen schnell umsetzen können,

  • aber auch robust genug, damit Sie sicher sein können, dass diese Änderungen kein unerwartetes Verhalten hervorrufen werden, 

  • und klar genug, damit man sich leicht zurechtfindet und es einem nicht über den Kopf wächst.

Um dies zu erreichen, ist die Entkopplung entscheidend. Um Ansichten, Ansichtsmodelle, Szenen und so weiter zu entkoppeln, müssen Sie herausfinden, wie Sie die Quelle der Wahrheit in der gesamten Anwendung gemeinsam nutzen können. Wenn Sie dies falsch handhaben oder nicht viel über den Zustand nachdenken, werden Sie es selbst mit UIKit-Anwendungen schwer haben, da dies zu einer Kopplung führt, die in Code resultiert, der schwer zu testen, zu lesen und zu verstehen ist:

ViewModels machen in diesem Fall viel zu viel. Sie geben untereinander Zustände weiter, was sie schwer testbar und schwer lesbar macht. Die Quelle der Wahrheit für diese Daten geht ebenfalls verloren, es ist unklar, woher der Zustand stammt und wem er gehört.

Die Zustandsverwaltung ist nichts Neues, das mit SwiftUI eingeführt wurde, sondern existiert schon seit Jahrzehnten, und es gibt keine spezifischen neuen Architekturanforderungen. Wenn Sie Funktionen unabhängig machen, Ihre Abhängigkeiten einfügen, testen und die Quelle der Wahrheit mit einem Repository-Muster definieren, können Sie Ihr System so gestalten, dass es leichter zu warten und zu erweitern ist, unabhängig davon, welches UI-Framework Sie wählen.

In diesem Beispiel gibt es eine strikte Grenze zwischen den Funktionen. ViewModels transformieren nur Daten und Aktionen zwischen Views und UseCases. Für jede Funktion gibt es eine klare Quelle der Wahrheit.  Die Isolierung führt zu Wiederverwendbarkeit und Testbarkeit.

State Management war schon immer wichtig in der App-Entwicklung, leider wurde es oft vernachlässigt oder schlecht gemacht. Deklarative UI-Konzepte bringen nichts Neues, da sie nur explizit betonen, was wichtig ist, um es gut zu handhaben, weil die Ansicht eine Funktion eines Zustands ist.

Alt und neu, zusammen

Bahnbrechende Technologien wie SwiftUI kommen nicht allzu oft auf den Markt, aber das bedeutet nicht, dass Ihre Anwendung und Architektur vollständig von dieser Technologie abhängig sein und/oder sich von ihr ableiten sollte. Im Gegenteil: Wenn Sie sich an die Trennung von Belangen halten und Ihren Code entkoppeln, erreichen Sie streng definierte Grenzen und können mit der Übernahme dieser Technologien beginnen, ohne den größten Teil Ihrer Anwendung neu schreiben zu müssen, und dies sogar schrittweise tun.​

SwiftUI ist eine brandneue Technologie, aber das bedeutet nicht, dass wir nicht von zeitlosen und klassischen Prinzipien profitieren können, die über viele Jahrzehnte geholfen haben, viele Herausforderungen zu bewältigen.  Altes und Neues können Hand in Hand gehen und sich gegenseitig ergänzen, damit wir brillante Apps entwickeln können.​



#development