Visão geral da arquitetura Clean Swift

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.

  1. O usuário digitou seu nome de usuário e senha e clicou no botão de autorização
  2. O ViewController aciona uma IBAction , após a qual uma estrutura é criada com os dados do usuário inseridos no TextFields (Modelos -> Solicitação)
  3. A estrutura criada é passada para o método fetchUser no Interactor'e
  4. O Interactor envia uma solicitação à rede e recebe uma resposta sobre o sucesso da autorização
  5. Com base nos dados recebidos, cria uma estrutura com o resultado (Modelos -> Resposta) e é passada para o método presentUser no Presenter'e
  6. O Presenter formata os dados conforme necessário e os retorna (Models -> ViewModel) para o método displayUser no ViewController'e
  7. 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


  1. Entendendo a arquitetura Clean Swift (você está aqui)
  2. Roteador e transmissão de dados na arquitetura Clean Swift
  3. Trabalhadores da arquitetura Clean Swift
  4. Teste de unidade na arquitetura Clean Swift
  5. Um exemplo de uma arquitetura simples de loja online Clean Swift

Todos os componentes da cena: Link
Ajuda para escrever um artigo: Bastien

Source: https://habr.com/ru/post/pt453986/


All Articles