Como escolher a arquitetura iOS apropriada (parte 2)

MVC, MVP, MVVM, VIPER ou VIP

Você pode consultar a primeira parte aqui.

As principais arquiteturas do iOS

Uma breve visão geral.

MVC

As camadas MVC são as seguintes:

M: lógica de negócios, camada de rede e camada de acesso a dados

V: Camada da interface do usuário (coisas do UIKit, Storyboards, Xibs)

C: Coordena a mediação entre Modelo e Vista.

Para entender o MVC, precisamos entender o contexto em que foi inventado. O MVC foi inventado nos velhos tempos de desenvolvimento da Web, em que o Views não tem estado. Nos velhos tempos, sempre que precisamos de uma alteração visual no site, o navegador recarrega todo o HTML novamente. Naquela época, não havia o conceito de estado de exibição sendo mantido e salvo.

Havia, por exemplo, alguns desenvolvedores que misturavam dentro do mesmo arquivo HTML, PHP e acesso ao banco de dados. Portanto, a principal motivação do MVC era separar a camada View da camada Model. Isso aumentou a testabilidade da camada Modelo. Supostamente no MVC, a camada View and Model não deve saber nada uma da outra. Para tornar isso possível, uma camada intermediária denominada Controller foi inventada. Este foi o SRP que foi aplicado.

Um exemplo do ciclo MVC:

  1. Uma ação / evento do usuário na Camada de Visualização (por exemplo: Ação de Atualização) é acionada e essa ação é comunicada ao Controlador
  2. O controlador que solicita dados para a camada de modelo
  3. Modele os dados retornados para o Controller
  4. O controlador diz para o View atualizar seu estado com o novo Data
  5. Ver atualizar seu estado

Apple MVC

No iOS, o View Controller é acoplado ao UIKit e à visualização do ciclo de vida, portanto, não é um MVC puro. No entanto, na definição do MVC, não há nada a dizer que o Controlador não pode conhecer a implementação específica da View ou Model. Seu principal objetivo é separar as responsabilidades da camada Model da camada View, para que possamos reutilizá-la e testar a camada Model isoladamente.

O ViewController contém a View e é dono do Model. O problema é que costumávamos escrever o código do controlador, bem como o código de exibição no ViewController.

O MVC geralmente cria o problema chamado Massive View Controller, mas isso só acontece e se torna algo sério em aplicativos com complexidade suficiente.

Existem alguns métodos que o desenvolvedor pode usar para tornar o View Controller mais gerenciável. Alguns exemplos:

  • Extrair a lógica do VC para outras classes, como a fonte de dados dos métodos de exibição de tabela e delegar para outros arquivos usando o padrão de design do delegado.
  • Crie uma separação de responsabilidades mais distinta com a composição (por exemplo, divida o VC em controladores de exibição filho).
  • Use o padrão de design do coordenador para remover a responsabilidade de implementar a lógica de navegação no VC
  • Use uma classe de wrapper DataPresenter que encapsule a lógica e transforme o modelo de dados em uma saída de dados representando os dados apresentados ao usuário final.

MVC vs MVP

Como você pode ver o diagrama do MVP é muito semelhante ao MVC

O MVC foi um passo à frente, mas ainda estava marcado pela ausência ou silêncio sobre algumas coisas.

Enquanto isso, a World Wide Web cresceu e muitas coisas na comunidade de desenvolvedores evoluíram. Por exemplo, os programadores começaram a usar o Ajax e carregam apenas partes das páginas em vez de toda a página HTML de uma só vez.

No MVC, acho que não há nada que indique que o Controller não deva conhecer a implementação específica do View (ausência).

O HTML fazia parte da camada View e muitos casos eram idiotas. Em alguns casos, ele recebe apenas eventos do usuário e exibe o conteúdo visual da GUI.

À medida que partes das páginas da Web começaram a ser carregadas em partes, essa segmentação levou à manutenção do estado de exibição e a uma maior necessidade de uma separação de responsabilidades da lógica de apresentação.

A lógica de apresentação é a lógica que controla como a interface do usuário deve ser exibida e como os elementos da interface do usuário interagem juntos. Um exemplo é a lógica de controle de quando um indicador de carregamento deve começar a mostrar / animar e quando deve parar de mostrar / animar.

No MVP e no MVVM, a View Layer deve ser burra, sem lógica ou inteligência, e no iOS, o View Controller deve fazer parte da View Layer. O fato de o View ser burro significa que mesmo a lógica da apresentação fica fora da camada View.

Um dos problemas do MVC é que não está claro onde a lógica da apresentação deve permanecer. Ele é simplesmente silencioso sobre isso. A lógica de apresentação deve estar na camada View ou na Model Model?

Se a função do modelo é fornecer apenas os dados "brutos", significa que o código na visualização seria:

Considere o seguinte exemplo: temos um usuário, com nome e sobrenome. Na tela, precisamos exibir o nome de usuário como "Sobrenome, Nome" (por exemplo, "Flores, Tiago").

Se a função do modelo é fornecer os dados "brutos", significa que o código na visualização seria:

deixe firstName = userModel.getFirstName ()
deixe lastName = userModel.getLastName ()
nameLabel.text = lastName + “,“ + firstName

Portanto, isso significa que seria responsabilidade da View lidar com a lógica da interface do usuário. Mas isso torna impossível a lógica da interface do usuário para o teste de unidade.

A outra abordagem é fazer com que o Modelo exponha apenas os dados que precisam ser exibidos, ocultando qualquer lógica de negócios da Visualização. Mas então, acabamos com modelos que lidam com a lógica de negócios e da interface do usuário. Seria testável por unidade, mas o Modelo acaba sendo implicitamente dependente da Visualização.

let name = userModel.getDisplayName ()
nameLabel.text = name

O MVP é claro sobre isso e a lógica da apresentação permanece na camada Presenter. Isso aumenta a testabilidade da camada Presenter. Agora, o modelo e a camada do apresentador são facilmente testáveis.

Normalmente, nas implementações do MVP, o View está oculto atrás de uma interface / protocolo e não deve haver referências ao UIKit no Presenter.

Outra coisa a ter em mente são as dependências transitivas.

Se o Controlador tiver uma Camada de Negócios como uma dependência e a Camada de Negócios tiver uma Camada de Acesso a Dados como uma dependência, o Controlador terá uma dependência transitiva para a Camada de Acesso a Dados. Como as implementações do MVP normalmente usam um contrato (protocolo) entre todas as camadas, não há dependências transitivas.

As diferentes camadas também mudam por diferentes motivos e em taxas diferentes. Portanto, quando você altera uma camada, não deseja que isso cause efeitos / problemas secundários nas outras camadas.

Os protocolos são mais estáveis ​​que as classes. Os protocolos não possuem detalhes de implementação e com os contratos, portanto, é possível alterar os detalhes de implementação de uma camada sem afetar as outras.

Portanto, os contratos (protocolos) criam uma dissociação entre as camadas.

MVP vs MVVM

Diagrama MVVM

Uma das principais diferenças entre o MVP e o MVVM é que, no MVP, o Presenter se comunica com o View por meio de interfaces, e no MVVM o View é orientado para alterações de dados e eventos.

No MVP, criamos uma ligação manual entre o Presenter e o View usando Interfaces / Protocolos.
No MVVM, fazemos a ligação automática de dados usando algo como RxSwift, KVO ou usamos um mecanismo com genéricos e fechamentos.

No MVVM, nem precisamos de um contrato (por exemplo: interface java / protocolo iOS) entre o ViewModel e o View, porque geralmente nos comunicamos através do Observer Design Pattern.

O MVP usa o padrão Delegate porque a camada Presenter delega ordens para a camada View, portanto, é necessário saber algo sobre a View, mesmo que seja apenas a assinatura da interface / protocolo. Pense na diferença entre o Notification Center e o TableView Delegates. O Centro de Notificação não precisa de interfaces para criar um canal de comunicação, mas o TableView Delegates usa um protocolo que as classes devem implementar.

Pense na lógica de apresentação de um indicador de carregamento. No MVP, o apresentador faz ViewProtocol.showLoadingIndicator. No MVVM, pode haver uma propriedade isLoading no ViewModel. A camada Exibir por meio de uma ligação automática de dados detecta quando essa propriedade é alterada e se atualiza. O MVP é mais imperativo que o MVVM porque o Presenter dá ordens.

O MVVM é mais sobre alterações de dados do que pedidos diretos, e fazemos associações entre alterações de dados e visualizamos atualizações. Se usarmos o RxSwift e o paradigma de programação reativa funcional junto ao MVVM, tornamos o código ainda menos imperativo e mais declarativo.

O MVVM é mais fácil de testar do que o MVP, porque o MVVM usa o Observer Design Pattern que transfere dados entre componentes de maneira dissociada.
Portanto, podemos testar apenas observando as alterações nos dados apenas comparando os dois objetos, em vez de criar zombarias nas chamadas de métodos para testar a comunicação entre o View e o Presenter.

PS: Fiz algumas atualizações no artigo que o fizeram crescer muito, por isso foi necessário dividi-lo em três partes. Você pode ler a parte três aqui.

A parte dois termina aqui. Todos os comentários são bem-vindos. A parte três falará sobre VIPER, VIP, programação reativa, trade-offs, restrições e senso contextual.

Obrigado pela leitura! Se você gostou deste artigo, bata palmas
So para que outras pessoas possam ler também :)