Back home

Padrões de arquitetura iOS

Padrões arquitetônicos do iOS

##Padrões de arquitetura iOS Padrões arquitetônicos do iOS

Desmistificando MVC, MVP, MVVM e VIPER Desmistificando MVC, MVP, MVVM e VIPER

Não perca o Roteiro para desenvolvedores iOS para 2018!

UPD: Slides que apresentei na NSLondon disponíveis aqui. UPD: Os slides que apresentei na NSLondon podem ser encontrados aqui.

Você se sente estranho ao fazer MVC no iOS? Tem dúvidas sobre como mudar para MVVM? Já ouviu falar do VIPER, mas não tem certeza se vale a pena? Está se sentindo estranho ao usar MVC no iOS? Tem dúvidas sobre como mudar para MVVM? Já ouviu falar do Viper, mas não tem certeza se vale a pena?

Continue lendo e você encontrará respostas para as perguntas acima, caso não se sinta à vontade para reclamar nos comentários. Continue lendo e você encontrará as respostas para as perguntas acima e, caso não encontre, fique à vontade para reclamar nos comentários.

Você está prestes a estruturar seu conhecimento sobre padrões arquitetônicos no ambiente iOS. Analisaremos brevemente alguns dos mais populares e os compararemos na teoria e na prática, examinando alguns pequenos exemplos. Siga os links se precisar de mais detalhes sobre algum deles em particular. Você desenvolverá seu conhecimento sobre padrões arquitetônicos no ambiente iOS. Revisaremos brevemente alguns exemplos populares e os compararemos na teoria e na prática. Se precisar de informações detalhadas sobre qualquer um, siga os links.

Dominar padrões de design pode ser viciante, então cuidado: você pode acabar se perguntando mais perguntas agora do que antes de ler este artigo, como estas: Dominar padrões de design pode ser viciante, então esteja avisado: você pode estar se perguntando mais perguntas agora do que antes de ler este artigo, como:

Quem deveria possuir a solicitação de rede: um modelo ou um controlador? Quem deve possuir as solicitações de rede: modelo ou controlador?

Como passo um modelo para um modelo de visualização de uma nova visualização? Como passo um modelo para o modelo de visualização de uma nova visualização?

Quem cria um novo módulo VIPER: Roteador ou Apresentador? Quem criou um novo módulo Viper: o roteador ou o programa de demonstração?

Por que se preocupar em escolher a arquitetura?

Por que você deveria se preocupar em escolher uma arquitetura?

Porque se você não fizer isso, um dia, depurando uma classe enorme com dezenas de coisas diferentes, você não conseguirá encontrar e corrigir nenhum bug em sua classe. Naturalmente, é difícil manter esta classe em mente como uma entidade completa, portanto, você sempre perderá alguns detalhes importantes. Se você já está nesta situação com sua aplicação, é muito provável que: Porque se você não fizer isso, um dia, ao depurar uma classe grande com muitas coisas diferentes, você não conseguirá encontrar e corrigir nenhum bug na classe. "Claro que é difícil lembrar da aula como um todo, então você sempre perde alguns detalhes importantes. Se sua aplicação já estiver nessa situação, é provável que:

  • Esta classe é a subclasse UIViewController. Esta classe é uma subclasse UIViewController.

  • Seus dados armazenados diretamente no UIViewController Seus dados são armazenados diretamente no UIViewController

  • Seus UIViews não fazem quase nada Seu uiview não faz quase nada

  • O modelo é uma estrutura de dados idiota O modelo é uma estrutura de dados idiota

  • Seus testes unitários não cobrem nada Seus testes unitários não cobrem nada

E isso pode acontecer, mesmo que você esteja seguindo as diretrizes da Apple e implementando o padrão MVC da Apple, então não se sinta mal. Há algo errado com o MVC da Apple, mas voltaremos ao assunto mais tarde. Isso pode acontecer mesmo se você seguir as diretrizes da Apple e implementar o padrão MVC da Apple, então não se sinta mal. O MVC da Apple tem alguns problemas, mas voltaremos a isso mais tarde.

Vamos definir características de uma boa arquitetura: Vamos definir as características de uma boa arquitetura:

  1. Distribuição equilibrada de responsabilidades entre entidades com funções rígidas. Distribuição equilibrada de responsabilidades entre entidades com funções rígidas.

  2. A testabilidade geralmente vem do primeiro recurso (e não se preocupe: é fácil com a arquitetura apropriada). A testabilidade geralmente vem do primeiro recurso (não se preocupe: é fácil com a arquitetura apropriada).

  3. Facilidade de uso e baixo custo de manutenção. Fácil de usar e baixo custo de manutenção.

Por que distribuição?

Por que distribuído?

A distribuição mantém uma carga razoável em nosso cérebro enquanto tentamos descobrir como as coisas funcionam. Se você acha que quanto mais você se desenvolve, melhor seu cérebro se adaptará à compreensão da complexidade, então você está certo. Mas essa habilidade não aumenta linearmente e atinge o limite muito rapidamente. Portanto, a maneira mais fácil de derrotar a complexidade é dividir as responsabilidades entre múltiplas entidades seguindo o princípio da responsabilidade única. A distribuição exerce uma carga considerável em nossos cérebros enquanto tentamos descobrir como as coisas funcionam. Se você acha que quanto mais você se desenvolve, melhor seu cérebro se adaptará para compreender a complexidade, você está certo. Mas esta capacidade não cresce linearmente e atingirá rapidamente um limite superior. Portanto, a maneira mais simples de superar a complexidade é dividir a responsabilidade entre múltiplas entidades seguindo o princípio da responsabilidade única.

Por que testabilidade?

Razões de testabilidade?

Essa geralmente não é uma pergunta para quem já se sentia grato pelos testes unitários, que falharam após adicionar novos recursos ou devido à refatoração de alguns meandros da classe. Isso significa que os testes evitaram que os desenvolvedores encontrassem problemas em tempo de execução, o que pode acontecer quando um aplicativo está no dispositivo de um usuário e a correção leva uma semana para chegar ao usuário. Isso geralmente não é um problema para aqueles que já são gratos por testes de unidade que falham após a adição de um novo recurso ou porque alguma complexidade da classe é refatorada. Isso significa que esses testes permitem que os desenvolvedores evitem a descoberta de problemas em tempo de execução, o que pode acontecer quando o aplicativo está no dispositivo do usuário e a correção leva uma semana para chegar ao usuário.

Por que facilidade de uso?

Por que facilidade de uso?Isso não requer resposta, mas vale ressaltar que o melhor código é aquele que nunca foi escrito. Portanto, quanto menos código você tiver, menos bugs terá. Isso significa que o desejo de escrever menos código nunca deve ser explicado apenas pela preguiça de um desenvolvedor, e você não deve preferir uma solução mais inteligente fechando os olhos para o custo de manutenção. Isso não exige resposta, mas vale a pena mencionar que o melhor código é aquele que nunca foi escrito. Portanto, menos código significa menos bugs. Isso significa que o desejo de escrever menos código não deve ser explicado apenas pela preguiça do desenvolvedor, e você não deve fechar os olhos aos custos de manutenção em favor de soluções mais inteligentes.

Fundamentos de MV(X)

Necessidades para VM (X)

Hoje em dia temos muitas opções quando se trata de padrões de projeto de arquitetura: Agora, quando se trata de padrões de projeto arquitetônico, temos muitas opções:

*MVC *MVP *MVVM *VÍPER

Os três primeiros pressupõem colocar as entidades do aplicativo em uma das três categorias: As três primeiras suposições são dividir as entidades de aplicação em três categorias:

  • Modelos — Responsável pelos dados do domínio ou por uma camada de acesso a dados que manipula os dados, pense nas classes ‘Person’ ou ‘PersonDataProvider’. Modelo – A camada de acesso a dados responsável pelos dados de domínio ou dados operacionais, considere as classes “Person” ou “PersonDataProvider”.

  • Views — Responsável pela camada de apresentação (GUI), para ambiente iOS pense em tudo começando com o prefixo ‘UI’. View – Responsável pela camada de apresentação (GUI), para ambientes iOS, considera tudo começando por “UI”.

  • Controlador/Apresentador/ViewModel — a cola ou mediador entre o Modelo e a Visualização, em geral responsável por alterar o Modelo reagindo às ações do usuário realizadas na Visualização e atualizando a Visualização com as alterações do Modelo. Controlador/Apresentador/ViewModel—A cola ou intermediário entre o modelo e a visualização, normalmente responsável por alterar o modelo em resposta às ações do usuário na visualização e atualizar a visualização com alterações do modelo.

Ter entidades divididas nos permite: A divisão de entidades nos permite:

  • entendê-los melhor (como já sabemos) Entenda-os melhor (como já sabemos)

  • reutilizá-los (principalmente aplicável à Visualização e ao Modelo) Reutilize-os (aplica-se principalmente a visualizações e modelos)

  • teste-os de forma independente testes independentes

Vamos começar com os padrões MV(X) e voltar ao VIPER mais tarde. Vamos começar com o modo MV(X) e retornar ao VIPER mais tarde.

###MVC

Como costumava ser

como foi no passado

Antes de discutir a visão do MVC da Apple, vamos dar uma olhada na [tradicional] (https://en.wikipedia.org/wiki/Model–view–controller). Antes de discutir a visão MVC da Apple, vamos dar uma olhada no MVC tradicional. MVC Tradicional

Nesse caso, a View não tem estado. Ele é simplesmente renderizado pelo Controller depois que o Model é alterado. Pense na página da web completamente recarregada quando você clica no link para navegar para outro lugar. Embora seja possível implementar o MVC tradicional em aplicativos iOS, não faz muito sentido devido ao problema arquitetônico — todas as três entidades estão fortemente acopladas, cada entidade sabe sobre as outras duas. Isso reduz drasticamente a capacidade de reutilização de cada um deles – não é isso que você deseja ter em seu aplicativo. Por esse motivo, deixamos de tentar escrever um exemplo canônico de MVC. Nesse caso, a visão é sem estado. Depois que o modelo é alterado, o controlador simplesmente o renderiza. Ao clicar em um link para navegar em outro lugar, considere recarregar totalmente a página da web. Embora seja possível implementar o MVC tradicional em um aplicativo iOS, isso não faz muito sentido devido a questões arquitetônicas - todas as três entidades estão fortemente acopladas e cada entidade conhece as outras duas. Isso reduz bastante sua capacidade de reutilização - algo que você não deseja ter em seu aplicativo. Por esse motivo, até deixamos de escrever exemplos canônicos de MVC.

O MVC tradicional não parece ser aplicável ao desenvolvimento moderno do iOS. O MVC tradicional não parece funcionar para o desenvolvimento moderno do iOS.

MVC da Apple

MVC da Apple

Expectativa

Expectativa

CacauMVC

O Controller é um mediador entre a View e o Model para que eles não se conheçam. O menos reutilizável é o Controlador e isso geralmente é bom para nós, já que precisamos ter um lugar para toda aquela lógica de negócios complicada que não se encaixa no Modelo. O controlador é um intermediário entre a visualização e o modelo, portanto, eles não se conhecem. A coisa menos reutilizável é o controlador, o que geralmente não é problema para nós porque temos que fornecer um local para toda a lógica de negócios complexa que não se enquadra no modelo.

Em teoria parece muito simples, mas você sente que algo está errado, certo? Você até ouviu pessoas abreviando MVC como Massive View Controller. Além disso, o descarregamento do controlador de visualização tornou-se um tópico importante para os desenvolvedores iOS. Por que isso acontece se a Apple simplesmente pegou o MVC tradicional e o melhorou um pouco? Em teoria, isso parece simples, mas você sente que algo está errado, certo? Você já ouviu pessoas abreviarem MVC para Large View Controller. Além disso, o descarregamento do controlador de visualização se tornou um tópico importante para desenvolvedores iOS. Por que isso aconteceria se a Apple simplesmente pegasse o MVC tradicional e fizesse algumas melhorias nele?

MVC da Apple

MVC da Apple

Realidade

realidade

Cacau MVC realistaCocoa MVC incentiva você a escrever controladores de visualização massivos, porque eles estão tão envolvidos no ciclo de vida da visualização que é difícil dizer que eles são separados. Embora ainda seja possível transferir parte da lógica de negócios e da transformação de dados para o Modelo, você não tem muita escolha quando se trata de transferir o trabalho para a Visualização. Na maioria das vezes, toda a responsabilidade da Visualização é enviar ações para o Controlador. O view controller acaba sendo um delegado e uma fonte de dados de tudo, e geralmente é responsável por despachar e cancelar as solicitações de rede e… você escolhe. Cocoa MVC incentiva você a escrever um grande número de controladores de visualização, porque eles são tão importantes no ciclo de vida da visualização que é difícil dizer que são independentes. Embora você ainda tenha a capacidade de vender alguma lógica de negócios e transformações de dados para o modelo, você não tem muitas opções quando se trata de transferir o trabalho para a visualização; na maioria das vezes, toda a responsabilidade da visualização é enviar ações ao controlador. O controlador de visualização é, em última análise, um delegado e a fonte de dados para tudo, e geralmente é responsável por despachar e cancelar solicitações de rede e… você escolhe.

Quantas vezes você já viu um código como este: Quantas vezes você já viu um código como este:

  
var userCell = tableView.dequeueReusableCellWithIdentifier("identifier") as UserCell
userCell.configureWithUser(user)

A célula, que é a View configurada diretamente com o Model, então as diretrizes do MVC são violadas, mas isso acontece o tempo todo, e geralmente as pessoas não acham que isso está errado. Se você seguir rigorosamente o MVC, então você deve configurar a célula a partir do controlador, e não passar o Model para o View, e isso aumentará ainda mais o tamanho do seu Controller. A célula é uma visualização configurada diretamente com o modelo, violando assim as diretrizes do MVC, mas isso acontece com tanta frequência que as pessoas geralmente não acham que esteja errado. Se você seguir estritamente o MVC, deverá configurar a célula do controlador em vez de passar o modelo para a visualização, o que aumentará ainda mais o tamanho do controlador.

Cocoa MVC é razoavelmente abreviado como Massive View Controller. Cocoa MVC é razoavelmente simplificado em controladores de visualização grande.

O problema pode não ser evidente até chegar ao teste de unidade (espero que seja no seu projeto). Como seu controlador de visualização está fortemente acoplado à visualização, torna-se difícil testá-lo porque você precisa ser muito criativo ao zombar das visualizações e de seu ciclo de vida, enquanto escreve o código do controlador de visualização de tal forma que sua lógica de negócios seja separada tanto quanto possível do código de layout da visualização. Este problema pode não ser óbvio antes do teste de unidade (espero que esteja no seu projeto). Como seu controlador de visualização está fortemente acoplado às visualizações, é difícil testá-lo porque, ao zombar, você precisa ser muito criativo com as visualizações e seu ciclo de vida e ao escrever o código do controlador de visualização de forma que sua lógica de negócios seja separada tanto quanto possível do código de layout da visualização.

Vamos dar uma olhada no exemplo simples do playground: Vejamos um exemplo simples de playground:

UPD: veja exemplos de código atualizados por Wasin Thonkaew UPD: veja o exemplo de código atualizado de Wasin Thonkaew


import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

class GreetingViewController : UIViewController { // View + Controller
    var person: Person!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
    
    func didTapButton(button: UIButton) {
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        self.greetingLabel.text = greeting
        
    }
    // layout code goes here
}
// Assembling of MVC
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
view.person = model;

Exemplo MVC

A montagem do MVC pode ser realizada no controlador de visualização de apresentação A montagem MVC pode ser realizada no controlador de visualização de apresentação

Isso não parece muito testável, certo? Podemos mover a geração de saudação para a nova classe GreetingModel e testá-la separadamente, mas não podemos testar nenhuma lógica de apresentação (embora não haja muita lógica no exemplo acima) dentro do GreetingViewController sem chamar os métodos relacionados ao UIView diretamente (viewDidLoad, didTapButton), o que pode causar o carregamento de todas as visualizações, e isso é ruim para o teste de unidade. Isso não parece muito viável, certo? Poderíamos substituir Greetings na nova classe GreetingModel e testá-la individualmente, mas não seríamos capazes de testar nenhuma lógica de apresentação (embora não exista muita lógica no exemplo acima) no GreetingViewController sem chamar diretamente o método relacionado ao UIView (viewDidLoad didTapButton), que pode fazer com que todas as visualizações sejam carregadas, o que não conduz ao teste de unidade.

Na verdade, carregar e testar UIViews em um simulador (por exemplo, iPhone 4S) não garante que funcionaria bem em outros dispositivos (por exemplo, iPad), então eu recomendo remover “Aplicativo Host” da configuração de destino do teste de unidade e executar seus testes sem que seu aplicativo seja executado no simulador. Na verdade, carregar e testar um UIView em um simulador (como um iPhone 4S) não garante que ele funcionará corretamente em outros dispositivos (como um iPad), então recomendo remover o “Aplicativo Host” da configuração do destino do teste de unidade e executar os testes com o aplicativo em execução no simulador.

As interações entre o View e o Controller não são realmente testáveis com testes unitários A interação entre a visualização e o controlador não pode ser testada por meio de testes unitários

Com tudo isso dito, pode parecer que Cocoa MVC é um padrão muito ruim para se escolher. Mas vamos avaliar isso em termos de recursos definidos no início do artigo: Resumindo, escolher Cocoa MVC parece ser um modelo muito ruim. Mas vamos avaliar a partir das características definidas no início do artigo:

  • **Distribuição ** — a Visualização e o Modelo estão de fato separados, mas a Visualização e o Controlador estão fortemente acoplados. Distribuição – As visualizações e os modelos são efetivamente separados, mas as visualizações e os controladores estão fortemente acoplados.

  • Testabilidade — devido à má distribuição você provavelmente testará apenas seu modelo. Testabilidade - Você só pode testar seu modelo devido à má distribuição.

  • Facilidade de uso — a menor quantidade de código entre outros padrões. Além disso, todos estão familiarizados com ele, portanto, é facilmente mantido mesmo por desenvolvedores inexperientes. Facilidade de uso – Quantidade mínima de código entre outros modos. Além disso, todos estão familiarizados com ele, por isso é fácil de manter, mesmo para desenvolvedores inexperientes.Cocoa MVC é o padrão de sua escolha se você não está pronto para investir mais tempo em sua arquitetura e sente que algo com custo de manutenção mais alto é um exagero para seu pequeno projeto favorito. Se você não está pronto para investir mais tempo na arquitetura e acha que algo com maior custo de manutenção é redundante para o seu pequeno projeto, então você pode escolher o padrão Cocoa MVC.

Cocoa MVC é o melhor padrão de arquitetura em termos de velocidade de desenvolvimento. Em termos de velocidade de desenvolvimento, Cocoa MVC é o melhor padrão de arquitetura.

###MVP

Promessas do Cocoa MVC cumpridas

A promessa do Cocoa MVC foi cumprida

Variante de visão passiva do MVP

Não se parece exatamente com o MVC da Apple? Sim, é verdade e seu nome é MVP (variante Passive View). Mas espere um minuto… Isso significa que o MVC da Apple é de fato um MVP? Não, não é, porque se você se lembra, o View está fortemente acoplado ao Controller, enquanto o mediador do MVP, Presenter, não tem nada a ver com o ciclo de vida do controlador de visualização, e o View pode ser simulado facilmente, portanto não há nenhum código de layout no Presenter, mas é responsável por atualizar o View com dados e estado. Não parece o MVC da Apple? Sim, é, e seu nome é MVP (Passive View Variant). Mas espere, isso significa que o MVC da Apple é na verdade MVP? Não, não é, porque se você se lembra, aí a visão está fortemente acoplada ao controlador, e o mediador do MVP, o apresentador, não tem nada a ver com o ciclo de vida do controlador de visão, a visão pode ser facilmente simulada, portanto não há código de layout no apresentador, mas é responsável por atualizar a visão com dados e estado.

E se eu te dissesse, o UIViewController é o View. Se eu te disser que UIViewController é uma view.

Em termos do MVP, as subclasses UIViewController são na verdade as Views e não os Presenters. Essa distinção fornece excelente testabilidade, que prejudica a velocidade de desenvolvimento, porque você precisa fazer vinculação manual de dados e eventos, como você pode ver no exemplo: No que diz respeito ao MVP, as subclasses UIViewController são, na verdade, visualizações em vez de apresentadores. Essa distinção fornece excelente testabilidade, mas tem um custo de velocidade de desenvolvimento porque você precisa fazer vinculação manual de dados e eventos, como você pode ver no exemplo:


import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

protocol GreetingView: class {
    func setGreeting(greeting: String)
}

protocol GreetingViewPresenter {
    init(view: GreetingView, person: Person)
    func showGreeting()
}

class GreetingPresenter : GreetingViewPresenter {
    unowned let view: GreetingView
    let person: Person
    required init(view: GreetingView, person: Person) {
        self.view = view
        self.person = person
    }
    func showGreeting() {
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        self.view.setGreeting(greeting)
    }
}

class GreetingViewController : UIViewController, GreetingView {
    var presenter: GreetingViewPresenter!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
    
    func didTapButton(button: UIButton) {
        self.presenter.showGreeting()
    }
    
    func setGreeting(greeting: String) {
        self.greetingLabel.text = greeting
    }
    
    // layout code goes here
}
// Assembling of MVP
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
let presenter = GreetingPresenter(view: view, person: model)
view.presenter = presenter

Exemplo de MVP

Nota importante sobre montagem

Notas importantes sobre montagem

O MVP é o primeiro padrão que revela o problema de montagem que ocorre por ter três camadas realmente separadas. Como não queremos que a View saiba sobre o Model, não é correto realizar a montagem na apresentação do controlador de visualização (que é a View), portanto, temos que fazer isso em outro lugar. Por exemplo, podemos fazer o serviço Router para todo o aplicativo, que será responsável por realizar a montagem e a apresentação View-to-View. Esse problema surge e deve ser abordado não apenas no MVP, mas também em todos os padrões a seguir. MVP é o primeiro padrão a revelar problemas de montagem devido a ter três camadas realmente independentes. Como não queremos que a view saiba sobre o modelo, é incorreto realizar a montagem quando o controlador da view (ou seja, a view) é apresentado, então temos que fazer isso em outro lugar. Por exemplo, podemos criar um serviço de roteador para toda a aplicação que será responsável por realizar a montagem e a apresentação view-to-view. Este problema não aparece apenas no MVP, mas deve ser resolvido em todos os padrões seguintes.

Vejamos os recursos do MVP: Vamos dar uma olhada nas características do MVP:

  • Distribuição — temos a maior parte das responsabilidades divididas entre o Apresentador e o Modelo, com a Visualização bastante idiota (no exemplo acima o Modelo também é burro). Distribuição - Distribuímos a maior parte das responsabilidades entre o apresentador e a modelo, e usamos visualizações bastante estúpidas (no exemplo acima, a modelo também era estúpida).

  • Testabilidade — é excelente, podemos testar a maior parte da lógica de negócios devido à visão burra. Testabilidade - Muito bom, podemos testar a maior parte da lógica de negócios graças a visualizações idiotas.

  • Fácil de usar— Em nosso exemplo irrealisticamente simples, a quantidade de código é duplicada em comparação com o MVC, mas, ao mesmo tempo, a ideia do MVP é muito clara. Facilidade de uso - Em nosso exemplo irrealisticamente simples, a quantidade de código é duplicada em comparação ao MVC, mas, ao mesmo tempo, o conceito de MVP é muito claro.

MVP no iOS significa excelente testabilidade e muito código. MVP no iOS significa excelente testabilidade e muito código.

###MVP jogador mais valioso

Com Bindings e Hooters

com corda e chapéu

Existe o outro tipo de MVP – o MVP do Controlador Supervisivo. Esta variante inclui vinculação direta de View e Model enquanto o Presenter (o controlador supervisor) ainda lida com ações da View e é capaz de alterar a View. Existe outro tipo de MVP – MVP do jogador supervisor. Esta variante inclui ligação direta da visualização e do modelo, enquanto o apresentador (controlador de monitoramento) ainda lida com as operações da visualização e é capaz de alterar a visualização.

Variante do apresentador supervisor do MVP

Mas, como já aprendemos antes, a separação vaga de responsabilidades é ruim, assim como o forte acoplamento da Visualização e do Modelo. Isso é semelhante a como as coisas funcionam no desenvolvimento de desktop Cocoa. Mas, como aprendemos antes, a separação vaga de responsabilidades é ruim, assim como o é o forte acoplamento entre visão e modelo. Isso é semelhante ao modo como funciona no desenvolvimento de desktop Cocoa.

Assim como acontece com o MVC tradicional, não vejo sentido em escrever um exemplo para a arquitetura falha. Tal como acontece com o MVC tradicional, não creio que seja necessário escrever exemplos para uma arquitetura falha.

###MVVM

O mais recente e o melhor do tipo MV(X) A melhor e mais recente categoria MV(X)O MVVM é o mais novo do tipo MV(X), portanto, esperamos que tenha surgido levando em consideração os problemas que o MV(X) enfrentava anteriormente. MVVM é o tipo MV(X) mais recente, então esperamos que seja lançado considerando os problemas que o MV(X) enfrentou antes.

Em teoria, o Model-View-ViewModel parece muito bom. A Visualização e o Modelo já nos são familiares, mas também o Mediador, representado como o Modelo de Visualização. Em teoria, model-view-viewmodel parece muito bom. Visões e modelos já nos são familiares, assim como as mediações, representadas como modelos de visão.

É muito semelhante ao MVP: Isso é muito semelhante ao MVP:

  • o MVVM trata o view controller como View MVVM trata controladores de visualização como visualizações

  • Não há acoplamento forte entre a Visualização e o Modelo Não há acoplamento forte entre a visualização e o modelo

Além disso, ele faz vinculação como a versão de supervisão do MVP; entretanto, desta vez não entre a Visualização e o Modelo, mas entre a Visualização e o Modelo de Visualização. Além disso, ele se vincula como a versão regulatória do MVP; entretanto, desta vez não é entre a visualização e o modelo, mas entre a visualização e o modelo de visualização.

Então, qual é o View Model na realidade do iOS? É basicamente uma representação UIKit independente de sua View e seu estado. O View Model invoca alterações no Model e se atualiza com o Model atualizado e, como temos uma ligação entre o View e o View Model, o primeiro é atualizado de acordo. O que é um modelo de visualização no iOS? É basicamente a representação independente do UIKit de uma visualização e seu estado. O modelo de visualização chama as alterações no modelo e se atualiza com o modelo atualizado, e como temos uma ligação entre a visualização e o modelo de visualização, o primeiro modelo será atualizado de acordo.

Ligações

vinculativo

Mencionei-os brevemente na parte do MVP, mas vamos discuti-los um pouco aqui. As ligações vêm prontas para o desenvolvimento do OS X, mas não as temos na caixa de ferramentas do iOS. É claro que temos o KVO e as notificações, mas elas não são tão convenientes quanto as ligações. Mencionei-os brevemente na seção MVP, mas vamos discuti-los aqui. As ligações já vêm prontas para o desenvolvimento do OS X, mas não as temos na caixa de ferramentas do iOS. Claro, temos KVO e notificações, mas eles não são tão convenientes quanto vinculativos.

Portanto, desde que não queiramos escrevê-los nós mesmos, temos duas opções: Então, se não quisermos escrever nós mesmos, temos duas opções:

  • Uma das bibliotecas de ligação baseadas em KVO, como RZDataBinding ou SwiftBond Uma das bibliotecas de ligação baseadas em KVO, como RZDataBinding ou SwiftBond

  • As feras da programação reativa funcional em grande escala, como ReactiveCocoa, RxSwift ou PromiseKit. Ferramentas de programação reativa completas, como ReactiveCocoa, RxSwift ou Promise eKit.

Na verdade, hoje em dia, se você ouvir “MVVM” – você pensa em ReactiveCocoa e vice-versa. Embora seja possível construir o MVVM com ligações simples, o ReactiveCocoa (ou irmãos) permitirá que você obtenha a maior parte do MVVM. Na verdade, hoje em dia, se você ouvir “MVVM”, você pensa em ReactiveCocoa, e vice-versa. Embora seja possível construir MVVM com ligações simples, o ReactiveCocoa (ou um irmão) permitirá que você obtenha a maior parte do MVVM.

Há uma verdade amarga sobre os quadros reativos: o grande poder traz consigo a grande responsabilidade. É muito fácil bagunçar as coisas quando você se torna reativo. Em outras palavras, se você fizer algo errado, poderá gastar muito tempo depurando o aplicativo, então dê uma olhada nesta pilha de chamadas. Há uma verdade dolorosa sobre as estruturas reativas: com grande poder vem grande responsabilidade. É fácil errar quando você reage exageradamente. Em outras palavras, se você fizer algo errado, poderá gastar muito tempo depurando seu aplicativo, então basta olhar para esta pilha de chamadas.

Depuração Reativa

Em nosso exemplo simples, a estrutura FRF ou mesmo o KVO é um exagero; em vez disso, pediremos explicitamente ao View Model para atualizar usando o método showGreeting e usaremos a propriedade simples para a função de retorno de chamada GreetingDidChange. Em nosso exemplo simples, a estrutura FRF ou mesmo KVO é redundante; em vez disso, solicitaremos explicitamente que o modelo de visualização seja atualizado usando o método showGreeting e uma propriedade simples usando a função de retorno de chamada GreetingDidChange.


import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

protocol GreetingViewModelProtocol: class {
    var greeting: String? { get }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set } // function to call when greeting did change
    init(person: Person)
    func showGreeting()
}

class GreetingViewModel : GreetingViewModelProtocol {
    let person: Person
    var greeting: String? {
        didSet {
            self.greetingDidChange?(self)
        }
    }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())?
    required init(person: Person) {
        self.person = person
    }
    func showGreeting() {
        self.greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
    }
}

class GreetingViewController : UIViewController {
    var viewModel: GreetingViewModelProtocol! {
        didSet {
            self.viewModel.greetingDidChange = { [unowned self] viewModel in
                self.greetingLabel.text = viewModel.greeting
            }
        }
    }
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self.viewModel, action: "showGreeting", forControlEvents: .TouchUpInside)
    }
    // layout code goes here
}
// Assembling of MVVM
let model = Person(firstName: "David", lastName: "Blaine")
let viewModel = GreetingViewModel(person: model)
let view = GreetingViewController()
view.viewModel = viewModel

Exemplo de MVVM

E novamente de volta à nossa avaliação de recursos: De volta à nossa avaliação de recursos:

  • Distribuição — não está claro em nosso pequeno exemplo, mas, na verdade, a Visão do MVVM tem mais responsabilidades do que a Visão do MVP. Porque o primeiro atualiza seu estado do View Model configurando ligações, enquanto o segundo apenas encaminha todos os eventos para o Presenter e não se atualiza. Distribuição – Não está claro no nosso pequeno exemplo, mas na verdade, a visão do MVVM tem mais responsabilidades do que a visão do MVP. Porque o primeiro atualiza o estado no modelo de visualização definindo a ligação, enquanto o segundo apenas encaminha todos os eventos para o apresentador sem se atualizar.

  • Testabilidade — o View Model não sabe nada sobre o View, o que nos permite testá-lo facilmente. A Visualização também pode ser testada, mas como depende do UIKit, você pode querer ignorá-la. Testabilidade – O modelo de visualização não sabe nada sobre a visualização, o que nos permite testá-lo facilmente. As visualizações também podem ser testadas, mas como dependem do UIKit, você pode querer ignorá-las.

  • Fácil de usar— tem a mesma quantidade de código que o MVP em nosso exemplo, mas no aplicativo real onde você teria que encaminhar todos os eventos da Visualização para o Presenter e atualizar a Visualização manualmente, o MVVM seria muito mais simples se você usasse ligações. Fácil de usar - tem a mesma quantidade de código que o MVP do nosso exemplo, mas em uma aplicação real você teria que encaminhar todos os eventos da view para o apresentador e atualizar a view manualmente, o MVVM seria mais fino se fossem usados ​​bindings.> O MVVM é muito atrativo, pois combina benefícios das abordagens citadas e, além disso, não requer código extra para as atualizações da View devido às ligações no lado da View. No entanto, a testabilidade ainda está em um bom nível. O MVVM é muito atraente porque combina as vantagens dos métodos acima e, devido à vinculação do lado da visualização, não requer código adicional para atualizar a visualização. Ainda assim, a testabilidade está em um bom nível.

VÍPER

Experiência de construção LEGO transferida para o design do aplicativo iOS Experiência de construção de LEGO transferida para design de aplicativo iOS

VIPER é nosso último candidato, o que é particularmente interessante porque não pertence à categoria MV(X). VIPER é nosso último candidato, o que é muito interessante porque não é da classe MV(X).

A esta altura, você deve concordar que a granularidade nas responsabilidades é muito boa. VIPER faz outra iteração na ideia de separação de responsabilidades, e desta vez temos cinco camadas. Agora você deve concordar que a granularidade nas responsabilidades é muito boa. VIPER tem mais uma iteração do conceito de separação de responsabilidades, desta vez temos 5 camadas.

VÍBORA

  • Interator — contém lógica de negócios relacionada aos dados (Entidades) ou rede, como criar novas instâncias de entidades ou buscá-las no servidor. Para esses fins você usará alguns Serviços e Gerentes que não são considerados parte do módulo VIPER, mas sim uma dependência externa. Interator — Contém lógica de negócios relacionada a dados (entidades) ou rede, como criar novas instâncias de entidades ou recuperá-las do servidor. Para isso você utilizará alguns serviços e gerenciadores que não são considerados parte do módulo VIPER, mas sim uma dependência externa.

  • Presenter significa - contém a lógica de negócios relacionada à UI (mas independente do UIKit), invoca métodos no Interactor. Presenter - Contém lógica de negócios relacionada à UI (mas independente do UIKit), chamando métodos no interator.

  • Entidades significa: seus objetos de dados simples, não a camada de acesso a dados, porque isso é responsabilidade do Interator. Entidades – seus objetos de dados normais, não a camada de acesso a dados, pois isso é de responsabilidade do interator.

  • Roteador — responsável pela transição entre os módulos VIPER. Roteador - Responsável pela transição entre os módulos do Viper.

Basicamente, o módulo VIPER pode ser uma tela ou toda a história do usuário da sua aplicação - pense na autenticação, que pode ser uma tela ou várias telas relacionadas. Quão pequenos devem ser os seus blocos “LEGO”? - Você decide. Basicamente, um módulo VIPER pode ser uma tela ou toda a história do usuário da aplicação - pense na autenticação, pode ser uma tela ou várias telas relacionadas. Quão pequenos devem ser os seus tijolos “LEGO”? - Você decide.

Se compararmos com o tipo MV(X), veremos algumas diferenças na distribuição de responsabilidades: Se compararmos isso com o tipo MV(X) veremos algumas diferenças na distribuição de responsabilidades:

  • A lógica do modelo (interação de dados) mudou para o Interator com as Entidades como estruturas de dados burras. A lógica do modelo (interação de dados) é convertida em estruturas de dados burras que interagem com entidades.

  • Somente as funções de representação da UI do Controller/Presenter/ViewModel foram movidas para o Presenter, mas não os recursos de alteração de dados. Somente a funcionalidade de apresentação da UI do controlador/apresentador/modelo de visualização é transferida para a apresentação, não a funcionalidade de modificação de dados.

  • VIPER é o primeiro padrão que aborda explicitamente a responsabilidade de navegação, que deve ser resolvida pelo roteador. VIPER é o primeiro padrão a lidar explicitamente com as responsabilidades de navegação, que devem ser tratadas pelo roteador.

A maneira correta de fazer o roteamento é um desafio para as aplicações iOS, os padrões MV(X) simplesmente não resolvem esse problema. O roteamento correto é um desafio para aplicativos iOS, e o padrão MV(X) simplesmente não pode resolver esse problema.

O exemplo não cobre roteamento ou interação entre módulos, pois esses tópicos não são cobertos pelos padrões MV(X). Este exemplo não envolve roteamento ou interações entre módulos, pois o padrão MV(X) não cobre esses tópicos.


import UIKit

struct Person { // Entity (usually more complex e.g. NSManagedObject)
    let firstName: String
    let lastName: String
}

struct GreetingData { // Transport data structure (not Entity)
    let greeting: String
    let subject: String
}

protocol GreetingProvider {
    func provideGreetingData()
}

protocol GreetingOutput: class {
    func receiveGreetingData(greetingData: GreetingData)
}

class GreetingInteractor : GreetingProvider {
    weak var output: GreetingOutput!
    
    func provideGreetingData() {
        let person = Person(firstName: "David", lastName: "Blaine") // usually comes from data access layer
        let subject = person.firstName + " " + person.lastName
        let greeting = GreetingData(greeting: "Hello", subject: subject)
        self.output.receiveGreetingData(greeting)
    }
}

protocol GreetingViewEventHandler {
    func didTapShowGreetingButton()
}

protocol GreetingView: class {
    func setGreeting(greeting: String)
}

class GreetingPresenter : GreetingOutput, GreetingViewEventHandler {
    weak var view: GreetingView!
    var greetingProvider: GreetingProvider!
    
    func didTapShowGreetingButton() {
        self.greetingProvider.provideGreetingData()
    }
    
    func receiveGreetingData(greetingData: GreetingData) {
        let greeting = greetingData.greeting + " " + greetingData.subject
        self.view.setGreeting(greeting)
    }
}

class GreetingViewController : UIViewController, GreetingView {
    var eventHandler: GreetingViewEventHandler!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
    
    func didTapButton(button: UIButton) {
        self.eventHandler.didTapShowGreetingButton()
    }
    
    func setGreeting(greeting: String) {
        self.greetingLabel.text = greeting
    }
    
    // layout code goes here
}
// Assembling of VIPER module, without Router
let view = GreetingViewController()
let presenter = GreetingPresenter()
let interactor = GreetingInteractor()
view.eventHandler = presenter
presenter.view = view
presenter.greetingProvider = interactor
interactor.output = presenter

Mais uma vez, voltando aos recursos: De volta aos recursos novamente:

  • Distribuição — Sem dúvida, o VIPER é campeão em distribuição de responsabilidades. Distribuição - Sem dúvida, o Viper é campeão na distribuição de responsabilidade.

  • Testabilidade — sem surpresas aqui, melhor distribuição — melhor testabilidade. Testabilidade – sem surpresas aqui, melhor distribuição – melhor testabilidade.

  • Fácil de usar — Finalmente, os dois itens acima estão no custo de manutenção, como você já adivinhou. Você tem que escrever uma grande quantidade de interface para classes com responsabilidades muito pequenas. Facilidade de uso – Finalmente, como você deve ter adivinhado, os dois itens mencionados acima são custos de manutenção. Você precisa escrever muitas interfaces para classes com responsabilidades muito pequenas.

E quanto ao LEGO?

E Lego?Ao usar o VIPER, você pode querer construir o Empire State Building com blocos de LEGO, e isso é um sinal de que você tem um problema. Talvez seja muito cedo para adotar o VIPER para sua aplicação e você deva considerar algo mais simples. Algumas pessoas ignoram isso e continuam atirando canhões contra os pardais. Presumo que eles acreditem que seus aplicativos se beneficiarão do VIPER pelo menos no futuro, mesmo que agora o custo de manutenção seja excessivamente alto. Se você acredita no mesmo, recomendo que experimente o Generamba – uma ferramenta para gerar esqueletos VIPER. Embora, para mim, pessoalmente, seja como usar um sistema automatizado de mira de canhão, em vez de simplesmente atirar com um estilingue. Ao usar o VIPER, você pode sentir que está construindo o Empire State Building com Legos, o que é um sinal de que você tem um problema. Talvez seja muito cedo para adotar o VIPER para sua aplicação e você deva considerar alguns métodos mais simples. Algumas pessoas ignoraram isso e continuaram a atirar nos pardais com seus canhões. Presumo que eles acreditem que sua aplicação se beneficiará do VIPER pelo menos no futuro, mesmo que os custos de manutenção sejam excessivamente altos agora. Se você pensa assim, sugiro que experimente o Generamba - uma ferramenta para gerar o esqueleto de uma cobra venenosa. Embora, para mim, pessoalmente, pareça atirar com um sistema de mira automática, em vez de um simples estilingue.

###Conclusão Conclusão

Passamos por vários padrões arquitetônicos e espero que você tenha encontrado algumas respostas para o que o incomoda, mas não tenho dúvidas de que você percebeu que não existe solução mágica, então escolher o padrão arquitetônico é uma questão de ponderar compensações em sua situação particular. Discutimos vários padrões arquitetônicos e espero que você tenha encontrado algumas respostas para as perguntas que o incomodam, mas tenho certeza que você percebeu que não existe solução mágica, então escolher um padrão arquitetônico é uma questão de pesar os prós e os contras em sua situação específica.

Portanto, é natural ter um mix de arquiteturas em um mesmo app. Por exemplo: você começou com MVC, então percebeu que uma tela específica se tornou muito difícil de manter de forma eficiente com o MVC e mudou para o MVVM, mas apenas para esta tela específica. Não há necessidade de refatorar outras telas para as quais o MVC realmente funcione bem, porque ambas as arquiteturas são facilmente compatíveis. Portanto, é natural misturar arquiteturas na mesma aplicação. Por exemplo: você começa com MVC, depois percebe que é difícil manter uma tela específica de forma eficiente usando MVC, então mude para MVVM, mas apenas para esta tela específica. Não há necessidade de refatorar outras telas onde o MVC realmente funciona bem, pois ambas as arquiteturas são facilmente compatíveis.

Torne tudo o mais simples possível, mas não mais simples - Albert Einstein Faça tudo o mais simples possível, mas não tão simples quanto possível - Albert Einstein

Obrigado por ler! Se você gostou deste artigo, bata palmas para que outras pessoas possam lê-lo também :) Obrigado por ler! Se você gostou deste post, por favor, bata palmas para que outras pessoas possam lê-lo também.

Siga-me no Twitter para mais designs e padrões para iOS. Siga-me no Twitter para mais designs e padrões para iOS.

Texto original