Hallo Leser!
In diesem Artikel werde ich über die Architektur von iOS-Anwendungen sprechen - 
Clean Swift . Wir werden die wichtigsten theoretischen Punkte betrachten und ein Beispiel in der Praxis analysieren.

Theorie
Zunächst werden wir die grundlegende Terminologie der Architektur analysieren. In 
Clean Swift besteht eine Anwendung aus Szenen, d.h. Jeder Anwendungsbildschirm ist eine Szene. Die Hauptinteraktion in der Szene durchläuft eine sequentielle Schleife zwischen den Komponenten von 
ViewController -> 
Interactor -> 
Presenter . Dies wird als 
VIP- Zyklus bezeichnet.
Die Brücke zwischen den Komponenten ist die 
Modelldatei , in der die übertragenen Daten gespeichert werden. Es gibt auch einen 
Router , der für die Übertragung und Übertragung von Daten zwischen Szenen verantwortlich ist, und 
Worker , der einen Teil der 
Interactor- Logik übernimmt.

Anzeigen
Storyboards, XIBs oder UI-Elemente, die über Code geschrieben wurden.
ViewController
Verantwortlich nur für die Konfiguration und Interaktion mit 
View . Der Controller sollte keine Geschäftslogik, Netzwerkinteraktionen, Computer usw. enthalten.
Seine Aufgabe ist es, Ereignisse mit Anzeigen, Anzeigen oder Senden von Daten (ohne Verarbeitung und Überprüfung) in 
Interactor zu verarbeiten .
Interactractor
Es enthält die Geschäftslogik der Szene.
Es funktioniert mit den Netzwerk-, Datenbank- und Gerätemodulen.
Interactor empfängt eine Anfrage von 
ViewController (mit oder ohne Daten), verarbeitet sie und überträgt bei Bedarf neue Daten an 
Presenter .
Moderator
Er ist mit der Aufbereitung von Daten für die Anzeige beschäftigt.
Fügen Sie beispielsweise einer Telefonnummer eine Maske hinzu oder machen Sie den ersten Buchstaben in der Großbuchstaben.
Es verarbeitet die von 
Interactor empfangenen Daten und sendet sie dann an den 
ViewController zurück .
Modelle
Eine Reihe von Strukturen zum Übertragen von Daten zwischen den Komponenten des 
VIP- Zyklus. Jeder Kreis des Zyklus hat 3 Arten von Strukturen:
- Anfrage - Datenstruktur (Text aus TextField usw.) zur Übertragung von ViewController an Interactor
- Antwort - Struktur mit Daten (aus dem Netzwerk heruntergeladen usw.) zur Übertragung von Interactor an Presenter
- ViewModel - Struktur mit verarbeiteten Daten (Textformatierung usw.) in Presenter für die Rückübertragung an ViewController
Arbeiter
Entlädt 
Interactor und übernimmt einen Teil der Geschäftslogik der Anwendung, wenn 
Interactor schnell wächst.
Sie können auch 
Worker erstellen, die allen Szenen gemeinsam sind, wenn ihre Funktionalität in mehreren Szenen verwendet wird.
In 
Worker können Sie beispielsweise die Logik für die Arbeit mit einem Netzwerk oder einer Datenbank festlegen.
Router
Die gesamte Logik, die für Übergänge und Datenübertragung zwischen Szenen verantwortlich ist, wird im 
Router entfernt .
Um das Bild des 
VIP- Zyklus zu verdeutlichen, gebe ich ein Standardbeispiel - die Autorisierung.
- Der Benutzer gab seinen Benutzernamen und sein Passwort ein und klickte auf die Autorisierungsschaltfläche
- Der ViewController löst eine IBAction aus. Anschließend wird eine Struktur mit den in TextFields eingegebenen Benutzerdaten erstellt (Models -> Request).
- Die erstellte Struktur wird in Interactor'e an die Methode fetchUser übergeben
- Interactor sendet eine Anfrage an das Netzwerk und erhält eine Antwort über den Erfolg der Autorisierung
- Erstellt basierend auf den empfangenen Daten eine Struktur mit dem Ergebnis (Modelle -> Antwort) und wird in Presenter'e an die Methode presentUser übergeben
- Presenter formatiert die Daten nach Bedarf und gibt sie (Modelle -> ViewModel) an die displayUser- Methode in ViewController'e zurück
- ViewController zeigt dem Benutzer die empfangenen Daten an. Im Falle einer Autorisierung kann ein Fehler angezeigt oder ein Übergang zu einer anderen Szene über den Router ausgelöst werden
So erhalten wir eine einheitliche und konsistente Struktur mit der Verteilung der Verantwortlichkeiten auf die Komponenten.
Übe
Schauen wir uns nun ein kleines praktisches Beispiel an, das zeigt, wie der 
VIP- Zyklus verläuft. In diesem Beispiel simulieren wir das Laden von Daten beim Öffnen einer Szene (Bildschirm). Ich habe die Hauptabschnitte des Codes mit Kommentaren markiert.
Der gesamte 
VIP- Zyklus ist an Protokolle gebunden, sodass alle Module ausgetauscht werden können, ohne die Anwendung zu stören.
Für 
ViewController wird ein 
DisplayLogic- Protokoll erstellt, dessen Link zum späteren Abruf an 
Presenter übergeben wird . Für 
Interactor werden zwei 
BusinessLogic- Protokolle erstellt, die für den Aufruf von Methoden aus 
ViewController und 
DataSource verantwortlich sind , um Daten zu speichern und eine andere Szene über den 
Router an 
Interactor zu übertragen . Presenter abonniert das 
PresentationLogic- Protokoll zum Aufrufen von 
Interactor . Das verbindende Element all dessen sind 
Modelle . Es enthält Strukturen, mit deren Hilfe Informationen zwischen den Komponenten des 
VIP- Zyklus ausgetauscht werden. Wir beginnen damit die Code-Analyse.

Modelle
Im folgenden Beispiel habe ich für die 
Home- Szene eine 
HomeModels- Datei erstellt, die eine Reihe von Abfragen für die 
VIP- Schleife enthält.
Die 
FetchUser- Anforderung ist für das Laden von Benutzerdaten verantwortlich, die wir weiter betrachten werden.
|  | // Models | 
|  | /// VIP | 
|  | enum HomeModels { | 
|  |  | 
|  | /// VIP | 
|  | enum FetchUser { | 
|  |  | 
|  | /// Interactor View Controller | 
|  | struct Request { | 
|  | let userName: String | 
|  | } | 
|  |  | 
|  | /// Presentor Interactor | 
|  | struct Response { | 
|  | let userPhone: String | 
|  | let userEmail: String | 
|  | } | 
|  |  | 
|  | /// View Controller Presentor | 
|  | struct ViewModel { | 
|  | let userPhone: String | 
|  | let userEmail: String | 
|  | } | 
|  | } | 
|  | } | 
ViewController
Wenn die Klasse initialisiert wird, instanziieren wir die 
Interactor- und 
Presenter- Klassen dieser Szene und stellen Abhängigkeiten zwischen ihnen her.
Weiter in 
ViewController'e gibt es nur einen Link zu 
Interactor . Über diesen Link erstellen wir eine Anfrage an die Methode 
fetchUser (request :) in 
Interactor , um den 
VIP- Zyklus zu starten.
Hier lohnt es sich zu beachten, wie die Anfrage an 
Interactor erfolgt. In der 
loadUserInfromation () -Methode erstellen wir eine Instanz der 
Request- Struktur, in der wir den Anfangswert übergeben. Es kann aus 
TextField , Tabellen usw. entnommen werden. Eine Instanz der 
Anforderungsstruktur wird an die Methode 
fetchUser (request :) übergeben , die sich im 
BusinessLogic- Protokoll unseres 
Interactors befindet .
|  | // ViewController | 
|  | /// | 
|  | protocol HomeDisplayLogic: class { | 
|  |  | 
|  | /// | 
|  | func displayUser(_ viewModel: HomeModels.FetchUser.ViewModel) | 
|  | } | 
|  |  | 
|  | final class HomeViewController: UIViewController { | 
|  |  | 
|  | /// Interactor'a | 
|  | var interactor: HomeBusinessLogic? | 
|  |  | 
|  | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { | 
|  | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) | 
|  | setup() | 
|  | } | 
|  |  | 
|  | required init?(coder aDecoder: NSCoder) { | 
|  | super.init(coder: aDecoder) | 
|  | setup() | 
|  | } | 
|  |  | 
|  | /// | 
|  | private func setup() { | 
|  | // VIP | 
|  | let interactor = HomeInteractor() | 
|  | let presenter = HomePresenter() | 
|  |  | 
|  | // | 
|  | interactor.presenter = presenter | 
|  | presenter.viewController = self | 
|  |  | 
|  | // Interactor View Controller | 
|  | self.interactor = interactor | 
|  | } | 
|  |  | 
|  | override func viewDidLoad() { | 
|  | super.viewDidLoad() | 
|  |  | 
|  | // | 
|  | fetchUser() | 
|  | } | 
|  |  | 
|  | /// Interactor | 
|  | private func loadUserInfromation() { | 
|  | // Interactor | 
|  | let request = HomeModels.FetchUser.Request(userName: "Aleksey") | 
|  |  | 
|  | // Interactor'a | 
|  | interactor?.fetchUser(request) | 
|  | } | 
|  | } | 
|  |  | 
|  | /// HomeDisplayLogic | 
|  | extension HomeViewController: HomeDisplayLogic { | 
|  |  | 
|  | func displayUser(_ viewModel: HomeModels.FetchUser.ViewModel) { | 
|  | print(viewModel) | 
|  | } | 
|  | } | 
Interactractor
Eine Instanz der 
Interactor- Klasse enthält einen Link zum 
PresentationLogic- Protokoll, unter dem 
Presenter signiert ist.
Die Methode 
fetchUser (request :) kann eine beliebige 
Datenladelogik enthalten. Zum Beispiel habe ich gerade Konstanten mit angeblich erhaltenen Daten erstellt.
Bei derselben Methode wird eine Instanz der 
Antwortstruktur erstellt und mit den zuvor erhaltenen Parametern gefüllt. 
Die Antwort wird mit der Methode 
presentUser (response :) an 
PresentationLogic übergeben . Mit anderen Worten, hier haben wir die Rohdaten erhalten und zur Verarbeitung an 
Presenter übergeben .
|  | // Interactor | 
|  | /// Interactor'a | 
|  | protocol HomeBusinessLogic: class { | 
|  |  | 
|  | /// | 
|  | func fetchUser(_ request: HomeModels.FetchUser.Request) | 
|  | } | 
|  |  | 
|  | final class HomeInteractor: HomeBusinessLogic { | 
|  |  | 
|  | /// | 
|  | var presenter: HomePresentationLogic? | 
|  |  | 
|  | func fetchUser(_ request: HomeModels.FetchUser.Request) { | 
|  | // | 
|  | // | 
|  | let userPhone = "+7 (999) 111-22-33" | 
|  | let userEmail = "im@alekseypleshkov.ru" | 
|  | // ... | 
|  | // Presentor' | 
|  | let response = HomeModels.FetchUser.Response(userPhone: userPhone, userEmail: userEmail) | 
|  |  | 
|  | // Presentor' | 
|  | presenter?.presentUser(response) | 
|  | } | 
|  | } | 
Moderator
Es verfügt über einen Link zum 
DisplayLogic- Protokoll, unter dem 
ViewController signiert ist. Es enthält keine Geschäftslogik, sondern formatiert nur die empfangenen Daten, bevor sie angezeigt werden. Im Beispiel haben wir die Telefonnummer formatiert, eine Instanz der 
ViewModel- Struktur vorbereitet und sie mit der 
Methode displayUser (viewModel :) im 
DisplayLogic- Protokoll an den 
ViewController übergeben , in der bereits Daten angezeigt werden.
|  | /// | 
|  | protocol HomePresentationLogic: class { | 
|  |  | 
|  | /// Interactor'a | 
|  | func presentUser(_ response: HomeModels.FetchUser.Response) | 
|  | } | 
|  |  | 
|  | final class HomePresenter: HomePresentationLogic { | 
|  |  | 
|  | /// View Controller'a | 
|  | weak var viewController: HomeDisplayLogic? | 
|  |  | 
|  | func presentUser(_ response: HomeModels.FetchUser.Response) { | 
|  | // | 
|  | let formattedPhone = response.userPhone.replacingOccurrences(of: "-", with: " ") | 
|  |  | 
|  | // ViewModel View Controller | 
|  | let viewModel = HomeModels.FetchUser.ViewModel(userPhone: formattedPhone, userEmail: response.userEmail) | 
|  |  | 
|  | // View Controller'a | 
|  | viewController?.displayUser(viewModel) | 
|  | } | 
|  | } | 
Fazit
Mit dieser Architektur hatten wir die Möglichkeit, Verantwortlichkeiten zu verteilen, das Testen der Anwendung zu vereinfachen, die Austauschbarkeit einzelner Abschnitte der Implementierung und den Standard für das Schreiben von Code für die Arbeit in einem Team einzuführen.
Vielen Dank für das Lesen bis zum Ende.
Artikelserie
- Grundlegendes zur Clean Swift-Architektur (Sie sind hier)
- Router- und Datenübergabe in einer sauberen Swift-Architektur
- Arbeiter sauberer schneller Architektur
- Unit-Tests in der Clean Swift-Architektur
- Ein Beispiel für eine einfache Online-Shop-Architektur Clean Swift
Alle Komponenten der Szene: 
LinkHilfe beim Schreiben eines Artikels: 
Bastien