Hola lector
En este artículo hablaré sobre la arquitectura de las aplicaciones de iOS:
Clean Swift . Consideraremos los principales puntos teóricos y analizaremos un ejemplo en la práctica.

Teoría
Para comenzar, analizaremos la terminología básica de la arquitectura. En
Clean Swift, una aplicación consta de escenas, es decir, Cada pantalla de aplicación es una escena. La interacción principal en la escena pasa por un bucle secuencial entre los componentes de
ViewController ->
Interactor ->
Presenter . Esto se llama ciclo
VIP .
El puente entre los componentes es el archivo
Modelos , que almacena los datos transmitidos. También hay un
enrutador , que es responsable de transferir y transferir datos entre escenas, y
Worker , que se hace cargo de parte de
la lógica de
Interactor .

Vista
Guiones gráficos, XIB o elementos de la IU escritos a través del código.
ViewController
Responsable solo de la configuración e interacción con
View . El controlador no debe contener ninguna lógica de negocios, interacciones de red, informática, etc.
Su tarea es procesar eventos con
Ver , mostrar o enviar datos (sin procesamiento y comprobaciones) en
Interactor .
Interactractor
Contiene la lógica de negocios de la escena.
Funciona con la red, la base de datos y los módulos del dispositivo.
Interactor recibe una solicitud de
ViewController (con datos o vacía), la procesa y, si es necesario, transfiere nuevos datos a
Presenter .
Presentador
Se dedica a la preparación de datos para su visualización.
Como ejemplo, agregue una máscara a un número de teléfono o escriba la primera letra en el título de la capital.
Procesa los datos recibidos de
Interactor y luego los devuelve al
ViewController .
Modelos
Un conjunto de estructuras para transferir datos entre los componentes del ciclo
VIP . Cada círculo del ciclo tiene 3 tipos de estructuras:
- Solicitud : estructura de datos (texto de TextField, etc.) para transferir de ViewController a Interactor
- Respuesta : estructura con datos (descargados de la red, etc.) para transferir de Interactor a Presentador
- ViewModel : estructura con datos procesados (formato de texto, etc.) en Presenter para transferirlos nuevamente a ViewController
Trabajador
Descarga
Interactor , asumiendo una parte de la lógica empresarial de la aplicación si
Interactor está creciendo rápidamente.
También puede crear
Trabajadores que sean comunes a todas las escenas, si su funcionalidad se usa en varias escenas.
Como ejemplo, en
Worker, puede hacer la lógica de trabajar con una red o base de datos.
Enrutador
Toda la lógica responsable de las transiciones y la transferencia de datos entre escenas se saca en el
enrutador .
Para aclarar la imagen del ciclo
VIP , daré un ejemplo estándar: autorización.
- El usuario ingresó su nombre de usuario y contraseña, hizo clic en el botón de autorización
- ViewController dispara IBAction , después de lo cual se crea una estructura con los datos de usuario ingresados en TextFields, (Modelos -> Solicitud)
- La estructura creada se pasa al método fetchUser en Interactor'e
- Interactor envía una solicitud a la red y recibe una respuesta sobre el éxito de la autorización
- En función de los datos recibidos, crea una estructura con el resultado (Modelos -> Respuesta) y se pasa al método presentUser en Presenter'e
- Presenter formatea los datos según sea necesario y los devuelve (Modelos -> ViewModel) al método displayUser en ViewController'e
- ViewController muestra los datos recibidos al usuario. En el caso de la autorización, se puede mostrar un error o se puede activar una transición a otra escena usando el enrutador
Por lo tanto, obtenemos una estructura única y consistente, con la distribución de responsabilidades en componentes.
Practica
Ahora echemos un vistazo a un pequeño ejemplo práctico que muestra cómo va el ciclo
VIP . En este ejemplo, simularemos la carga de datos al abrir una escena (pantalla). Marqué las secciones principales del código con comentarios.
Todo el ciclo
VIP está vinculado a protocolos, lo que brinda la capacidad de reemplazar cualquier módulo sin interrumpir la aplicación.
Se crea un protocolo
DisplayLogic para
ViewController , un enlace que se pasa a
Presenter para su posterior recuperación. Para
Interactor , se crean dos protocolos
BusinessLogic , que se encargan de llamar a los métodos desde
ViewController y
DataSource , para almacenar datos y transferir otra escena a través de
Router a
Interactor . El presentador se suscribe al protocolo
PresentationLogic para llamar desde
Interactor . El elemento de conexión de todo esto es
Modelos . Contiene estructuras con la ayuda de las cuales se intercambia información entre los componentes del ciclo
VIP . Comenzaremos el análisis de código con él.

Modelos
En el siguiente ejemplo, para la escena
Home , creé un archivo
HomeModels que contiene un conjunto de consultas para el bucle
VIP .
La solicitud
FetchUser será responsable de cargar los datos del usuario, lo que consideraremos más a fondo.
| // 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
Cuando se inicializa la clase, instanciamos las clases de
Interactor y
Presentador de esta escena y establecemos dependencias entre ellas.
Además, en
ViewController'e solo hay un enlace a
Interactor . Usando este enlace, crearemos una solicitud para el
método fetchUser (request :) en
Interactor para iniciar el ciclo
VIP .
Aquí vale la pena prestar atención a cómo se produce la solicitud a
Interactor . En el método
loadUserInfromation () , creamos una instancia de la estructura
Request , donde pasamos el valor inicial. Se puede tomar de
TextField , tablas, etc. Se pasa una instancia de la estructura
Request al método
fetchUser (request :) , que se encuentra en el protocolo
BusinessLogic de nuestro
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
Una instancia de la clase
Interactor contiene un enlace al protocolo
PresentationLogic , bajo el cual
se firma
Presenter .
El
método fetchUser (request :) puede contener cualquier lógica de carga de datos. Por ejemplo, acabo de crear constantes, con datos supuestamente obtenidos.
En el mismo método, se crea una instancia de la estructura de
Respuesta y se completa con los parámetros obtenidos anteriormente.
La respuesta se pasa a
PresentationLogic utilizando el
método presentUser (respuesta :) . En otras palabras, aquí obtuvimos los datos sin procesar y los pasamos a
Presenter para su procesamiento.
| // 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) |
| } |
| } |
Presentador
Tiene un enlace al protocolo
DisplayLogic , bajo el cual
se firma
ViewController . No contiene ninguna lógica empresarial, pero solo formatea los datos recibidos antes de mostrarlos. En el ejemplo, formateamos el número de teléfono, preparamos una instancia de la estructura
ViewModel y la pasamos al
ViewController usando el
método displayUser (viewModel :) en el protocolo
DisplayLogic , donde los datos ya se muestran.
| /// |
| 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) |
| } |
| } |
Conclusión
Con esta arquitectura, tuvimos la oportunidad de distribuir responsabilidades, mejorar la conveniencia de probar la aplicación, introducir la capacidad de reemplazo de secciones individuales de la implementación y el estándar para escribir código para trabajar en equipo.
Gracias por leer hasta el final.
Serie de artículos
- Comprender la arquitectura Clean Swift (Usted está aquí)
- Enrutador y paso de datos en la arquitectura Clean Swift
- Trabajadores de la arquitectura Clean Swift
- Pruebas unitarias en arquitectura Clean Swift
- Un ejemplo de una arquitectura simple de tienda en línea Clean Swift
Todos los componentes de la escena:
enlaceAyuda para escribir un artículo:
Bastien