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