Bonjour lecteur!
Dans cet article, je parlerai de l'architecture des applications iOS -
Clean Swift . Nous allons considérer les principaux points théoriques et analyser un exemple en pratique.

Théorie
Pour commencer, nous analyserons la terminologie de base de l'architecture. Dans
Clean Swift, une application se compose de scènes, c'est-à-dire chaque écran d'application est une scène. L'interaction principale dans la scène passe par une boucle séquentielle entre les composants de
ViewController ->
Interactor ->
Presenter . C'est ce qu'on appelle un cycle
VIP .
Le pont entre les composants est le fichier
Modèles , qui stocke les données transmises. Il existe également un
routeur , qui est responsable du transfert et du transfert des données entre les scènes, et
Worker , qui reprend une partie de
la logique d'
Interactor .

Afficher
Storyboards, XIB ou éléments d'interface utilisateur écrits via du code.
ViewController
Responsable uniquement de la configuration et de l'interaction avec
View . Le contrôleur ne doit contenir aucune logique métier, interactions réseau, informatique, etc.
Sa tâche consiste à traiter des événements avec
Visualiser , afficher ou envoyer des données (sans traitement ni vérification) dans
Interactor .
Interactractor
Il contient la logique métier de la scène.
Il fonctionne avec les modules réseau, base de données et appareils.
Interactor reçoit une demande de
ViewController (avec des données ou vide), la traite et, si nécessaire, transfère de nouvelles données à
Presenter .
Présentateur
Il est engagé dans la préparation des données à afficher.
Par exemple, ajoutez un masque à un numéro de téléphone ou faites la première lettre dans la capitale du titre.
Il traite les données reçues d'
Interactor , puis les renvoie au
ViewController .
Les modèles
Un ensemble de structures pour transférer des données entre les composants du cycle
VIP . Chaque cercle du cycle a 3 types de structures:
- Demande - Structure de données (texte de TextField, etc.) pour le transfert de ViewController à Interactor
- Réponse - Structure avec des données (téléchargées du réseau, etc.) pour le transfert d' Interactor à Presenter
- ViewModel - Structure avec des données traitées (formatage de texte, etc.) dans Presenter pour un retour à ViewController
Ouvrier
Décharge
Interactor , prenant une partie de la logique métier de l'application si
Interactor se développe rapidement.
Vous pouvez également créer des
travailleurs communs à toutes les scènes, si leur fonctionnalité est utilisée dans plusieurs scènes.
Par exemple, dans
Worker, vous pouvez définir la logique de travail avec un réseau ou une base de données.
Routeur
Toute la logique responsable des transitions et du transfert de données entre les scènes est supprimée dans le
routeur .
Pour clarifier l'image du cycle
VIP , je vais donner un exemple standard: l'autorisation.
- L'utilisateur a entré son nom d'utilisateur et son mot de passe, cliqué sur le bouton d'autorisation
- Le ViewController déclenche une IBAction , après quoi une structure est créée avec les données utilisateur entrées dans TextFields, (Modèles -> Demande)
- La structure créée est passée à la méthode fetchUser dans Interactor'e
- L'interacteur envoie une demande au réseau et reçoit une réponse sur le succès de l'autorisation
- Sur la base des données reçues, crée une structure avec le résultat (Modèles -> Réponse) et est passée à la méthode presentUser dans Presenter'e
- Presenter formate les données selon les besoins et les renvoie (Modèles -> ViewModel) à la méthode displayUser dans ViewController'e
- ViewController affiche les données reçues à l'utilisateur. Dans le cas d'une autorisation, une erreur peut s'afficher ou une transition vers une autre scène peut être déclenchée à l'aide du routeur
Ainsi, nous obtenons une structure unique et cohérente, avec la répartition des responsabilités en composants.
Pratique
Voyons maintenant un petit exemple pratique qui montre comment se déroule le cycle
VIP . Dans cet exemple, nous simulerons le chargement des données lors de l'ouverture d'une scène (écran). J'ai marqué les principales sections du code avec des commentaires.
L'ensemble du cycle
VIP est lié à des protocoles, ce qui permet de remplacer n'importe quel module sans perturber l'application.
Un protocole
DisplayLogic est créé pour
ViewController , un lien vers lequel est transmis à
Presenter pour un rappel ultérieur. Pour
Interactor , deux protocoles
BusinessLogic sont créés, qui sont chargés d'appeler les méthodes de
ViewController et
DataSource , de stocker les données et de transférer une autre scène via le
routeur vers
Interactor . Le présentateur souscrit au protocole
PresentationLogic pour appeler depuis
Interactor . L'élément de connexion de tout cela est les
modèles . Il contient des structures à l'aide desquelles des informations sont échangées entre les composants du cycle
VIP . Nous allons commencer l'analyse du code avec.

Les modèles
Dans l'exemple ci-dessous, pour la scène
Home , j'ai créé un fichier
HomeModels qui contient un ensemble de requêtes pour la boucle
VIP .
La demande
FetchUser sera responsable du chargement des données utilisateur, que nous examinerons plus loin.
| // 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
Lorsque la classe est initialisée, nous instancions les classes
Interactor et
Presenter de cette scène et établissons des dépendances entre elles.
Plus loin dans
ViewController'e, il n'y a qu'un lien vers
Interactor . En utilisant ce lien, nous allons créer une demande à la
méthode fetchUser (request :) dans
Interactor pour démarrer le cycle
VIP .
Ici, il convient de prêter attention à la façon dont la demande à
Interactor se produit. Dans la méthode
loadUserInfromation () , nous créons une instance de la structure
Request , où nous transmettons la valeur initiale. Il peut être extrait de
TextField , de tableaux, etc. Une instance de la structure
Request est transmise à la
méthode fetchUser (request :) , qui se trouve dans le protocole
BusinessLogic de notre
Interactor .
| // 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
Une instance de la classe
Interactor contient un lien vers le protocole
PresentationLogic , sous lequel
Presenter est signé.
La
méthode fetchUser (request :) peut contenir n'importe quelle logique de chargement de données. Par exemple, je viens de créer des constantes, avec des données prétendument obtenues.
Dans la même méthode, une instance de la structure
Response est créée et remplie avec les paramètres obtenus précédemment.
La réponse est transmise à
PresentationLogic à l'aide de la
méthode presentUser (response :) . En d'autres termes, nous avons obtenu ici les données brutes et les avons transmises à
Presenter pour traitement.
| // 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) |
| } |
| } |
Présentateur
Il a un lien vers le protocole
DisplayLogic , sous lequel
ViewController est signé. Il ne contient aucune logique métier, mais formate uniquement les données reçues avant de les afficher. Dans l'exemple, nous avons formaté le numéro de téléphone, préparé une instance de la structure
ViewModel et l'
avons transmise au
ViewController à l'aide de la
méthode displayUser (viewModel :) du protocole
DisplayLogic , où les données sont déjà affichées.
| /// |
| 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) |
| } |
| } |
Conclusion
Avec cette architecture, nous avons eu l'opportunité de répartir les responsabilités, d'améliorer la commodité du test de l'application, d'introduire la remplaçabilité des sections individuelles de l'implémentation et la norme d'écriture de code pour travailler en équipe.
Merci d'avoir lu jusqu'au bout.
Série d'articles
- Comprendre l'architecture Swift propre (vous êtes ici)
- Routeur et transmission de données dans une architecture propre et rapide
- Travailleurs de l'architecture propre et rapide
- Tests unitaires dans l'architecture Clean Swift
- Un exemple d'une architecture de boutique en ligne simple Clean Swift
Toutes les composantes de la scène:
LienAide à la rédaction d'un article:
Bastien