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