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