Olá leitor!
Neste artigo, falarei sobre a arquitetura dos aplicativos iOS - 
Clean Swift . Vamos considerar os principais pontos teóricos e analisar um exemplo na prática.

Teoria
Para começar, analisaremos a terminologia básica da arquitetura. No 
Clean Swift, um aplicativo consiste em cenas, ou seja, cada tela do aplicativo é uma cena. A principal interação na cena passa por um loop seqüencial entre os componentes do 
ViewController -> 
Interactor -> 
Presenter . Isso é chamado de ciclo 
VIP .
A ponte entre os componentes é o arquivo 
Modelos , que armazena os dados transmitidos. Há também um 
roteador , responsável por transferir e transferir dados entre as cenas, e 
Worker , que assume parte da lógica 
do Interactor .

Ver
Storyboards, XIBs ou elementos da interface do usuário gravados por meio de código.
ViewController
Responsável apenas pela configuração e interação com o 
View . O controlador não deve conter nenhuma lógica comercial, interações de rede, computação e assim por diante.
Sua tarefa é processar eventos com 
Exibir , exibir ou enviar dados (sem processamento e verificações) no 
Interactor .
Interactractor
Ele contém a lógica de negócios da cena.
Funciona com os módulos de rede, banco de dados e dispositivos.
O Interactor recebe uma solicitação do 
ViewController (com dados ou vazio), processa e, se necessário, transfere novos dados para o 
Presenter .
Apresentador
Ele está envolvido na preparação de dados para exibição.
Como exemplo, adicione uma máscara a um número de telefone ou insira a primeira letra no título.
Ele processa os dados recebidos do 
Interactor e os envia de volta ao 
ViewController .
Modelos
Um conjunto de estruturas para transferir dados entre os componentes do ciclo 
VIP . Cada círculo do ciclo possui 3 tipos de estruturas:
- Request - Estrutura de dados (texto de TextField, etc.) para transferência do ViewController para o Interactor
- Resposta - Estrutura com dados (baixados da rede etc.) para transferência do Interactor para o Presenter
- ViewModel - Estrutura com dados processados (formatação de texto etc.) no Presenter para a transferência de volta ao ViewController
Trabalhador
Descarrega o 
Interactor , assumindo parte da lógica de negócios do aplicativo se o 
Interactor estiver crescendo rapidamente.
Você também pode criar 
Trabalhadores comuns a todas as cenas, se sua funcionalidade for usada em várias cenas.
Como exemplo, no 
Trabalhador, você pode fazer a lógica de trabalhar com uma rede ou banco de dados.
Roteador
Toda lógica responsável pelas transições e transferência de dados entre as cenas é retirada no 
roteador .
Para esclarecer a imagem do ciclo 
VIP , darei um exemplo padrão - autorização.
- O usuário digitou seu nome de usuário e senha e clicou no botão de autorização
- O ViewController aciona uma IBAction , após a qual uma estrutura é criada com os dados do usuário inseridos no TextFields (Modelos -> Solicitação)
- A estrutura criada é passada para o método fetchUser no Interactor'e
- O Interactor envia uma solicitação à rede e recebe uma resposta sobre o sucesso da autorização
- Com base nos dados recebidos, cria uma estrutura com o resultado (Modelos -> Resposta) e é passada para o método presentUser no Presenter'e
- O Presenter formata os dados conforme necessário e os retorna (Models -> ViewModel) para o método displayUser no ViewController'e
- ViewController exibe os dados recebidos para o usuário. No caso de autorização, um erro pode ser exibido ou uma transição para outra cena pode ser acionada usando o Roteador
Assim, obtemos uma estrutura única e consistente, com a distribuição de responsabilidades em componentes.
Prática
Agora, vamos dar uma olhada em um pequeno exemplo prático que mostra como ocorre o ciclo 
VIP . Neste exemplo, simularemos o carregamento de dados ao abrir uma cena (tela). Marquei as principais seções do código com comentários.
Todo o ciclo 
VIP está vinculado a protocolos, o que fornece a capacidade de substituir qualquer módulo sem interromper o aplicativo.
Um protocolo 
DisplayLogic é criado para o 
ViewController , um link ao qual é passado para o 
Presenter para recuperação posterior. Para o 
Interactor , são criados dois protocolos 
BusinessLogic , responsáveis por chamar métodos do 
ViewController e 
DataSource , para armazenar dados e transferir outra cena pelo 
roteador para o 
Interactor . O Presenter assina o protocolo 
PresentationLogic para ligar do 
Interactor . O elemento de conexão de tudo isso é 
modelos . Ele contém estruturas com as quais as informações são trocadas entre os componentes do ciclo 
VIP . Iniciaremos a análise de código com ele.

Modelos
No exemplo abaixo, para a cena 
inicial , criei um arquivo 
HomeModels que contém um conjunto de consultas para o loop 
VIP .
A solicitação 
FetchUser será responsável por carregar os dados do usuário, que consideraremos mais adiante.
|  | // 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
Quando a classe é inicializada, instanciamos as classes 
Interactor e 
Presenter dessa cena e estabelecemos dependências entre elas.
Além disso, no 
ViewController'e, existe apenas um link para o 
Interactor . Usando esse link, criaremos uma solicitação para o 
método fetchUser (request :) no 
Interactor para iniciar o ciclo 
VIP .
Aqui vale a pena prestar atenção em como ocorre a solicitação ao 
Interactor . No método 
loadUserInfromation () , criamos uma instância da estrutura 
Request , onde passamos o valor inicial. Pode ser obtido no 
TextField , tabelas e assim por diante. Uma instância da estrutura de 
solicitação é passada para o 
método fetchUser (request :) , que está no protocolo 
BusinessLogic do nosso 
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
Uma instância da classe 
Interactor contém um link para o protocolo 
PresentationLogic , sob o qual o 
Presenter é assinado.
O 
método fetchUser (request :) pode conter qualquer lógica de carregamento de dados. Por exemplo, eu apenas criei constantes, com dados supostamente obtidos.
No mesmo método, uma instância da estrutura de 
resposta é criada e preenchida com os parâmetros obtidos anteriormente. 
A resposta é passada para 
PresentationLogic usando o método 
presentUser (response :) . Em outras palavras, aqui obtivemos os dados brutos e os passamos para o 
Presenter para processamento.
|  | // 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) | 
|  | } | 
|  | } | 
Apresentador
Ele possui um link para o protocolo 
DisplayLogic , sob o qual o 
ViewController é assinado. Ele não contém nenhuma lógica comercial, mas apenas formata os dados recebidos antes de exibi-los. No exemplo, formatamos o número de telefone, preparamos uma instância da estrutura 
ViewModel e passamos para o 
ViewController usando o método 
displayUser (viewModel :) no protocolo 
DisplayLogic , onde os dados já estão sendo exibidos.
|  | /// | 
|  | 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) | 
|  | } | 
|  | } | 
Conclusão
Com essa arquitetura, tivemos a oportunidade de distribuir responsabilidades, melhorar a conveniência de testar o aplicativo, introduzir a substituibilidade de seções individuais da implementação e o padrão para escrever código para trabalhar em equipe.
Obrigado por ler até o fim.
Série de artigos
- Entendendo a arquitetura Clean Swift (você está aqui)
- Roteador e transmissão de dados na arquitetura Clean Swift
- Trabalhadores da arquitetura Clean Swift
- Teste de unidade na arquitetura Clean Swift
- Um exemplo de uma arquitetura simples de loja online Clean Swift
Todos os componentes da cena: 
LinkAjuda para escrever um artigo: 
Bastien