Schreiben von iOS-Anwendungen mit dem Redux-Muster

Bild

In letzter Zeit war ich mehr in die Front-End-Entwicklung als in die mobile Entwicklung involviert und stieß auf einige sehr interessante Designmuster, die ich bereits kannte, aber nicht wirklich darauf einging ... bis jetzt.

Aber jetzt macht all dies Sinn, nachdem ich die Entwicklung in React mehrere Wochen lang verwendet habe, kann ich jetzt nicht mehr zu meinen alten Entwicklungsmethoden für iOS zurückkehren. Ich werde nicht auf Javascript (AKA React Native) umsteigen, um mobile Anwendungen zu entwickeln, aber hier sind einige Dinge, die ich gelernt habe.

Bild

Zurück zur iOS-Entwicklung, habe ich ein neues Projekt erstellt und angefangen, ReSwift zu erkunden. Dies ist eine Implementierung des Flux- und Redux- Musters in Swift. Und es funktioniert ganz einfach: Ich habe die Architektur der JavaScript-Anwendung mehrmals geklont, jetzt habe ich einen globalen Status und meine Controller hören nur diesen Status ab. Die Controller selbst bestehen aus verschiedenen Präsentationskomponenten, die ein sehr spezifisches Verhalten enthalten.

Bild

Alle Zustandsänderungen werden an einem Ort im Reduzierstück vorgenommen . Eine für den Unterzustand. Sie können alle Aktionen an einem Ort sehen. Kein Netzwerkcode oder aufrufende Controller mehr, keine Mutationen von Objekten mehr in Ansichten. Kein Spaghetti-Code mehr. Es gibt nur einen Status , und es ist wahr, dann abonnieren Ihre verschiedenen Präsentationskomponenten (und ich bestehe darauf) verschiedene Teile des Status und reagieren entsprechend. Dies ist einfach die beste Architektur für eine starke Modellanwendung.

Beispielsweise. Früher waren die Controller der Anmeldeansicht mit vielen Codezeilen, verschiedenen Steuerungsstatus, Fehlerbehandlung usw. gefüllt. Jetzt sieht es so aus: (Als Beispiel)

import UIKit import Base import ReSwift class LoginViewController: UIViewController { @IBOutlet var usernameField: UITextField! @IBOutlet var passwordField: UITextField! override func viewDidLoad() { super.viewDidLoad() store.subscribe(self) {state in state.usersState } } @IBAction func onLoginButton(_ sender: Any) { store.dispatch(AuthenticatePassword(username: usernameField.text!, password: passwordField.text!)) } @IBAction func onTwitterButton(_ sender: Any) { store.dispatch(AuthenticateTwitter()) } @IBAction func onFacebookButton(_ sender: Any) { store.dispatch(AuthenticateFacebook(from: self)) } } // MARK: - State management extension LoginViewController: StoreSubscriber { func newState(state: UsersState) { if let error = state.authState.error { presentError(error: error.type, viewController: self, completion: nil) } if let _ = state.getCurrentUser { self.dismiss(animated: true, completion: nil) } } } 

Controller und Darstellungen von Versandaktionen im globalen Status. Diese Aktionen funktionieren tatsächlich mit dem Netzwerk oder starten die verschiedenen Teile, die Ihre Anwendung zum Konvertieren in den neuen Status benötigt.

Eine Aktion kann eine andere Aktion auslösen. So geschieht dies für eine Netzwerkanforderung. Sie haben beispielsweise eine FetchUser- Aktion (ID: String) und eine Aktion, die Sie in einem Reduzierer abfangen, der wie SetUser aussieht (Benutzer: Benutzer). Im Reduzierer sind Sie für das Zusammenführen / Zusammenführen eines neuen Objekts mit Ihrem aktuellen Status verantwortlich.

Zuerst benötigen Sie den Status . Mein Beispiel konzentriert sich auf das Benutzerobjekt. Der Status könnte also ungefähr so ​​aussehen:

 struct UsersState { var users: [String: User] = [:] } 

Sie müssen über eine Datei verfügen, die alle Netzwerkaktivitäten für das Benutzerobjekt enthält.

 struct FetchUser: Action { init(user: String) { GETRequest(path: "users/\(user)").run { (response: APIResponse<UserJSON>) in store.dispatch(SetUser(user: response.object)) } } } 

Sobald die Anforderung abgeschlossen ist, ruft sie eine andere Aktion auf . Diese Aktion ist tatsächlich leer. Sie sollte beispielsweise in UsersActions referenziert werden. Diese Aktion beschreibt das Ergebnis, auf das sich der Reduzierer verlassen muss, um den Status zu ändern.

 struct SetUser: Action { let user: UserJSON? } 

Und schließlich wird die wichtigste Arbeit in UsersReducer erledigt . Sie müssen die Aktion erfassen und einige Arbeiten gemäß ihrem Inhalt ausführen :

 func usersReducer(state: UsersState?, action: Action) -> UsersState { var state = state ?? initialUsersState() switch action { case let action as SetUser: if let user = action.user { state.users[user.id] = User(json: user) } default: break } return state } 

Jetzt müssen Sie nur noch den Status in Controllern oder Ansichten abonnieren / abonnieren. Wenn er sich ändert, extrahieren Sie die erforderlichen Informationen und erhalten Sie neue Werte!

 class UserViewController: UIViewController { var userId: String? { didSet { if let id = userId { store.dispatch(FetchUser(user: id)) } } } var user: User? { didSet { if let user = user { setupViewUser(user: user) } } } override func viewDidLoad() { super.viewDidLoad() store.subscribe(self) {state in state.usersState } } func setupViewUser(user: User) { //Do uour UI stuff. } } extension UserViewController: StoreSubscriber { func newState(state: UsersState) { self.user = state.users[userId!] } } 

Aber jetzt sollten Sie sich die ReSwift- Beispiele ansehen, um ein tieferes Verständnis zu erlangen. Ich plane, eine Open-Source-Anwendung (eigentlich ein Spiel) mit diesem Entwurfsmuster zu veröffentlichen. Im Moment zeigt der Code jedoch eine sehr grobe Vorstellung davon, wie dies alles zusammenarbeitet.

Dies ist noch eine sehr frühe Architektur in Glose-Büchern, aber wir können es kaum erwarten, dass die Anwendung mit dieser Architektur in Produktion geht.

Ich bin der Meinung, dass die Entwicklung von Anwendungen mit diesem Muster viel Zeit und Mühe spart. Es wird ein wenig mehr Arbeit erfordern als ein dumm einfacher REST-Client , da der Client-Status etwas logischer ist, aber am Ende sparen Sie wertvolle Zeit für das Debuggen. Sie können viele Elemente lokal ändern, und es gibt keine kaskadierenden Änderungen mehr zwischen Controllern und Ansichten. Reproduzieren Sie den Status in Sicherungsreihenfolge, archivieren Sie ihn, erstellen Sie Middleware usw. Der Anwendungsdatenstrom ist klar, zentralisiert und einfach.

Das Redux- Muster verleiht der Anwendung ein wenig Struktur. Ich mache schon sehr lange reine MVC. Ich bin sicher, dass Sie eine saubere Codebasis erstellen können, aber Sie neigen dazu, Gewohnheiten zu entwickeln, die oft mehr schaden als nützen. Sie können sogar noch einen Schritt weiter gehen und Redux vollständig implementieren, indem Sie Ihre Benutzeroberfläche (z. B. View Controller, Alert Viewer, Routing Controller) in einem separaten Status steuern. Dies alles habe ich jedoch noch nicht erreicht.

Und die Tests ... Unit-Tests sind jetzt einfach zu implementieren, da Sie lediglich die eingegebenen Daten mit den im globalen Status enthaltenen Daten vergleichen müssen, damit die Tests Scheinaktionen senden und dann prüfen können, ob der Status mit Ihrem übereinstimmt will.

Im Ernst, das ist die Zukunft. Die Zukunft ist für Redux :)

Source: https://habr.com/ru/post/de464561/


All Articles