So wählen Sie die geeignete iOS-Architektur aus (Teil 2)

MVC, MVP, MVVM, VIPER oder VIP

Sie können Teil eins hier konsultieren.

Die wichtigsten iOS-Architekturen

Ein kurzer Überblick.

MVC

Die MVC-Schichten lauten wie folgt:

M: Geschäftslogik, Netzwerkschicht und Datenzugriffsschicht

V: UI-Ebene (UIKit-Objekte, Storyboards, Xibs)

C: Koordiniert die Vermittlung zwischen Modell und Ansicht.

Um MVC zu verstehen, müssen wir den Kontext verstehen, in dem es erfunden wurde. MVC wurde in den alten Webentwicklungstagen erfunden, in denen Views keinen Status hat. In alten Zeiten lädt der Browser jedes Mal, wenn wir eine visuelle Änderung in der Website benötigen, den gesamten HTML-Code erneut. Zu dem Zeitpunkt gab es keine Vorstellung davon, dass der Ansichtsstatus beibehalten und gespeichert wurde.

Es gab zum Beispiel einige Entwickler, die dieselben HTML-Dateien, PHP- und Datenbankzugriffe verwendeten. Die Hauptmotivation von MVC bestand also darin, die Ansichtsebene von der Modellebene zu trennen. Dies erhöhte die Testbarkeit der Model-Ebene. Angeblich sollten in MVC der View- und der Model-Layer nichts voneinander wissen. Um dies zu ermöglichen, wurde eine Zwischenschicht namens Controller erfunden. Dies war die SRP, die angewendet wurde.

Ein Beispiel für den MVC-Zyklus:

  1. Eine Benutzeraktion / ein Benutzerereignis in der Ansichtsebene (z. B. "Aktion aktualisieren") wird ausgelöst und diese Aktion wird dem Controller mitgeteilt
  2. Der Controller, der Daten an die Modellebene sendet
  3. Modellieren Sie die zurückgegebenen Daten an den Controller
  4. Der Controller sagt, dass für die Ansicht sein Status mit den neuen Daten aktualisiert wird
  5. Ansicht aktualisieren seinen Zustand

Apple MVC

In iOS ist der View Controller mit dem UIKit und der Lebenszyklusansicht gekoppelt, sodass es sich nicht um reines MVC handelt. In der MVC-Definition gibt es jedoch nichts zu sagen, dass der Controller die View- oder Model-spezifische Implementierung nicht kennen kann. Sein Hauptzweck ist es, die Zuständigkeiten der Model-Ebene von der View-Ebene zu trennen, damit wir sie wiederverwenden und die Model-Ebene isoliert testen können.

Der ViewController enthält die Ansicht und besitzt das Modell. Das Problem ist, dass wir sowohl den Controller-Code als auch den View-Code in den ViewController schreiben.

MVC verursacht häufig das so genannte Massive View Controller-Problem, das jedoch nur in Apps mit ausreichender Komplexität auftritt und zu einer ernsten Angelegenheit wird.

Es gibt einige Methoden, mit denen der Entwickler den View Controller übersichtlicher gestalten kann. Einige Beispiele:

  • Extrahieren der VC-Logik für andere Klassen wie die Datenquelle der Tabellenansichtsmethoden und Delegieren für andere Dateien mithilfe des Delegieren-Entwurfsmusters.
  • Erstellen Sie eine klarere Aufteilung der Zuständigkeiten in Bezug auf die Zusammensetzung (z. B. Aufteilen der VC in untergeordnete Ansichtssteuerungen).
  • Verwenden Sie das Koordinator-Entwurfsmuster, um die Verantwortung für die Implementierung der Navigationslogik in der virtuellen Steuerung zu entfernen
  • Verwenden Sie eine DataPresenter-Wrapper-Klasse, die die Logik kapselt und das Datenmodell in eine Datenausgabe umwandelt, die die dem Endbenutzer präsentierten Daten darstellt.

MVC gegen MVP

Wie Sie das Diagramm von MVP sehen können, ist MVC sehr ähnlich

Das MVC war ein Schritt nach vorne, aber es war immer noch durch Abwesenheit oder Schweigen über einige Dinge gekennzeichnet.

In der Zwischenzeit wuchs das World Wide Web und es entwickelten sich viele Dinge in der Entwickler-Community. Zum Beispiel haben die Programmierer angefangen, Ajax zu verwenden und nur Teile von Seiten anstelle der gesamten HTML-Seite auf einmal zu laden.

In MVC gibt es meines Erachtens keinen Hinweis darauf, dass der Controller die spezifische Implementierung von View nicht kennen sollte (Abwesenheit).

HTML war Teil der View-Ebene und viele Fälle waren so doof. In einigen Fällen empfängt es nur Ereignisse vom Benutzer und zeigt den visuellen Inhalt der GUI an.

Als Teile der Webseiten in Teile geladen wurden, führte diese Segmentierung zur Beibehaltung des Ansichtsstatus und zu einem größeren Bedarf an einer Trennung der Verantwortlichkeiten für die Präsentationslogik.

Präsentationslogik ist die Logik, die steuert, wie die Benutzeroberfläche angezeigt werden soll und wie Benutzeroberflächenelemente miteinander interagieren. Ein Beispiel ist die Steuerlogik, wann ein Ladeindikator zu zeigen / animieren beginnen soll und wann er zu zeigen / animieren aufhören soll.

In MVP und MVVM sollte die View-Ebene so doof sein, dass sie weder Logik noch Intelligenz enthält, und in iOS sollte der View-Controller Teil der View-Ebene sein. Die Tatsache, dass View dumm ist, bedeutet, dass selbst die Präsentationslogik außerhalb der View-Ebene bleibt.

Eines der Probleme von MVC ist, dass nicht klar ist, wo die Präsentationslogik bleiben soll. Darüber schweigt er einfach. Soll sich die Präsentationslogik in der Ansichtsebene oder in der Modellebene befinden?

Wenn die Rolle des Modells darin besteht, nur die "Rohdaten" bereitzustellen, bedeutet dies, dass der Code in der Ansicht wie folgt lautet:

Betrachten Sie das folgende Beispiel: Wir haben einen Benutzer mit Vor- und Nachnamen. In der Ansicht müssen wir den Benutzernamen als "Nachname, Vorname" anzeigen (z. B. "Flores, Tiago").

Wenn die Rolle des Modells darin besteht, die "Rohdaten" bereitzustellen, bedeutet dies, dass der Code in der Ansicht wie folgt lautet:

let firstName = userModel.getFirstName ()
let lastName = userModel.getLastName ()
nameLabel.text = Nachname + “,“ + Vorname

Dies bedeutet, dass es in der Verantwortung von View liegt, die Benutzeroberflächenlogik zu handhaben. Dies macht es jedoch unmöglich, die Benutzeroberflächenlogik einem Komponententest zu unterziehen.

Der andere Ansatz besteht darin, das Modell nur die Daten anzeigen zu lassen, die angezeigt werden müssen, und die Geschäftslogik vor der Ansicht zu verbergen. Aber dann haben wir Modelle, die sowohl die Geschäftslogik als auch die Benutzeroberflächenlogik verarbeiten. Es wäre eine testbare Einheit, aber dann ist das Modell implizit von der Ansicht abhängig.

let name = userModel.getDisplayName ()
nameLabel.text = name

Dem MVP ist das klar und die Präsentationslogik bleibt in der Presenter-Ebene. Dies erhöht die Testbarkeit der Presenter-Ebene. Jetzt können Modell und Presenter-Layer problemlos getestet werden.

Normalerweise ist in MVP-Implementierungen die Ansicht hinter einer Schnittstelle / einem Protokoll verborgen, und im Presenter sollten keine Verweise auf das UIKit vorhanden sein.

Eine andere Sache zu beachten ist die transitiven Abhängigkeiten.

Wenn der Controller eine Business-Schicht als Abhängigkeit und die Business-Schicht eine Datenzugriffsschicht als Abhängigkeit hat, hat der Controller eine transitiven Abhängigkeit für die Datenzugriffsschicht. Da die MVP-Implementierungen normalerweise einen Vertrag (ein Protokoll) zwischen allen Ebenen verwenden, gibt es keine transitiven Abhängigkeiten.

Die verschiedenen Schichten ändern sich auch aus unterschiedlichen Gründen und mit unterschiedlichen Raten. Wenn Sie also eine Ebene wechseln, soll dies keine sekundären Effekte / Probleme in den anderen Ebenen verursachen.

Protokolle sind stabiler als Klassen. Die Protokolle enthalten keine Implementierungsdetails und sind nicht mit den Verträgen verknüpft. Daher ist es möglich, die Implementierungsdetails einer Ebene zu ändern, ohne die anderen Ebenen zu beeinflussen.

Die Verträge (Protokolle) schaffen also eine Entkopplung zwischen den Schichten.

MVP vs MVVM

MVVM-Diagramm

Einer der Hauptunterschiede zwischen MVP und MVVM besteht darin, dass der Presenter in MVP über Schnittstellen mit der Ansicht kommuniziert und in MVVM die Ansicht auf Daten- und Ereignisänderungen ausgerichtet ist.

In The MVP stellen wir eine manuelle Bindung zwischen Presenter und View mithilfe von Schnittstellen / Protokollen her.
In der MVVM nehmen wir eine automatische Datenbindung mit RxSwift, KVO oder einem Mechanismus mit Generika und Closures vor.

In der MVVM benötigen wir sogar keinen Vertrag (z. B. Java-Schnittstelle / iOS-Protokoll) zwischen ViewModel und View, da wir normalerweise über das Observer Design Pattern kommunizieren.

Der MVP verwendet das Delegieren-Muster, da die Presenter-Ebene Befehle an die Ansichtsebene delegiert. Daher muss er etwas über die Ansicht wissen, auch wenn es sich nur um die Schnittstellen- / Protokollsignatur handelt. Denken Sie an den Unterschied zwischen Notification Center- und TableView-Delegierten. Das Notification Center benötigt keine Schnittstellen, um einen Kommunikationskanal zu erstellen. TableView Delegates verwendet jedoch ein Protokoll, das die Klassen implementieren sollten.

Denken Sie an die Präsentationslogik eines Ladeanzeigers. In MVP führt der Präsentator ViewProtocol.showLoadingIndicator aus. In MVVM befindet sich möglicherweise eine isLoading-Eigenschaft im ViewModel. Der View-Layer erkennt durch eine automatische Datenbindung, wenn sich diese Eigenschaft ändert, und aktualisiert sich selbst. MVP ist zwingender als MVVM, da der Präsentator Befehle erteilt.

In MVVM geht es mehr um Datenänderungen als um direkte Bestellungen, und wir verknüpfen Datenänderungen mit Ansichtsaktualisierungen. Wenn wir RxSwift und ein funktionales reaktives Programmierparadigma zusammen mit MVVM verwenden, haben wir den Code noch weniger zwingend und deklarativer gemacht.

MVVM ist einfacher zu testen als MVP, da MVVM das Observer Design Pattern verwendet, das Daten entkoppelt zwischen Komponenten überträgt.
Wir können also testen, indem wir nur die Änderungen in den Daten betrachten, indem wir die beiden Objekte vergleichen, anstatt die Methodenaufrufe zu verspotten, um die Kommunikation zwischen View und Presenter zu testen.

PS: Ich habe einige Aktualisierungen an dem Artikel vorgenommen, durch die er sehr gewachsen ist. Es war daher notwendig, ihn in drei Teile zu teilen. Den dritten Teil können Sie hier lesen.

Teil zwei endet hier. Alle Rückmeldungen sind willkommen. In Teil drei geht es um VIPER, VIP, Reactive Programming, Trade-Offs, Constraints und Contextual Sense.

Danke fürs Lesen! Wenn Ihnen dieser Artikel gefallen hat, klatschen Sie bitte
damit andere es auch lesen können :)