Écriture d'applications iOS à l'aide du modèle Redux

image

Récemment, j'ai été plus impliqué dans le développement front-end que mobile, et je suis tombé sur des modèles de conception très intéressants que je connaissais déjà, mais je ne les ai pas vraiment approfondis ... jusqu'à présent.

Mais maintenant, tout cela a du sens, après avoir utilisé le développement dans React pendant plusieurs semaines, je ne peux plus revenir à mes anciennes façons de développer pour iOS. Je ne passerai pas en javascript (AKA React Native) pour développer des applications mobiles, mais voici certaines choses que j'ai apprises.

image

Revenant au développement iOS, j'ai créé un nouveau projet et commencé à explorer ReSwift , c'est une implémentation du modèle Flux et Redux dans Swift. Et cela fonctionne tout simplement, j'ai cloné l'architecture de l'application JavaScript plusieurs fois, maintenant j'ai un état global, et mes contrôleurs écoutent simplement cet état. Les contrôleurs eux-mêmes sont constitués de divers composants de présentation qui encapsulent un comportement très spécifique.

image

Tous les changements d' état sont effectués au même endroit, dans le réducteur . Un pour le sous-état. Vous pouvez voir toutes les actions au même endroit. Plus de code réseau ou de contrôleurs d'appel, plus de mutations d'objets dans les vues. Plus de code spaghetti. Il n'y a qu'un seul état , et c'est vrai, alors vos différents composants de présentation (et j'insiste dessus) souscrivent à différentes parties de l' état et réagissent en conséquence. Il s'agit simplement de la meilleure architecture pour une application de modèle solide.

Pour un exemple. Auparavant, les contrôleurs de la vue de connexion étaient remplis de nombreuses lignes de code, de divers états de contrôle, de gestion des erreurs, etc. ... Maintenant, cela ressemble à ceci: (à titre d'exemple)

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) } } } 

Contrôleurs et représentations des actions de répartition à l'état global, ces actions fonctionnent réellement avec le réseau ou lancent les différentes parties que votre application devra convertir au nouvel état.

Une action peut déclencher une autre action, c'est ainsi que cela se passe pour une requête réseau, par exemple, vous avez une action FetchUser (id: String) et une action que vous interceptez dans un réducteur qui ressemble à SetUser (user: User). Dans le réducteur, vous êtes responsable de la fusion / fusion d'un nouvel objet avec votre état actuel.

Vous devez d'abord l' état , mon exemple se concentrera autour de l'objet utilisateur , donc l' état pourrait ressembler à ceci:

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

Vous devez disposer d'un fichier qui encapsule toutes les activités réseau de l'objet utilisateur.

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

Dès que la requête est terminée, elle appelle une autre action , cette action est en fait vide, elle doit être référencée, par exemple, dans UsersActions. Cette action décrit le résultat sur lequel le réducteur doit s'appuyer pour changer d'état.

 struct SetUser: Action { let user: UserJSON? } 

Et enfin, le travail le plus important est effectué dans UsersReducer , vous devez attraper l'action et effectuer un travail conformément à son contenu:

 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 } 

Maintenant, tout ce qui est nécessaire est de s'abonner / s'abonner à l'état dans les contrôleurs ou les vues, et quand il change, extrayez les informations nécessaires et obtenez de nouvelles valeurs!

 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!] } } 

Mais maintenant, vous devriez jeter un œil aux exemples ReSwift pour une compréhension plus approfondie, je prévois de publier une application open source (en fait un jeu) en utilisant ce modèle de conception. Mais pour l'instant, le code affiche une idée très grossière de la façon dont tout cela fonctionne ensemble.

Il s'agit encore d'une architecture très ancienne dans les livres Glose, mais nous ne pouvons pas attendre que l'application soit mise en production en utilisant cette architecture.

Je pense que le développement d'applications utilisant ce modèle permettra d'économiser beaucoup de temps et d'efforts. Cela prendra un peu plus de travail qu'un client REST stupidement simple, car il y aura un peu plus de logique à l'intérieur de l'état du client, mais au final, cela vous fera gagner un temps précieux pour le débogage. Vous pourrez modifier de nombreux éléments localement et il n'y aura plus de changements en cascade entre les contrôleurs et les vues. Reproduisez l'état dans l'ordre de sauvegarde, archivez-le, créez un middleware, etc. Le flux de données de l'application est clair, centralisé et simple.

Le modèle Redux ajoute un peu de structure à l'application. Je fais du MVC pur depuis très longtemps, je suis sûr que vous pouvez créer une base de code propre, mais vous avez tendance à développer des habitudes qui font souvent plus de mal que de bien. Vous pouvez même aller plus loin et implémenter complètement Redux en contrôlant votre interface utilisateur (comme les contrôleurs de vue, les visualiseurs d'alertes, les contrôleurs de routage) dans un état séparé, mais je n'ai pas encore atteint tout cela).

Et les tests ... Les tests unitaires sont désormais faciles à implémenter, car il vous suffit de comparer les données que vous saisissez avec les données contenues dans l'état global, afin que les tests puissent envoyer des actions simulées, puis vérifier si l'état correspond à ce que vous envie.

Sérieusement, c'est l'avenir. L'avenir est pour Redux :)

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


All Articles