SwiftUI e arquiteturas: MVC

Compartilhe

Compartilhar no facebook
Compartilhar no google
Compartilhar no twitter
Compartilhar no linkedin

SwiftUI e arquiteturas: MVC

Uma das notícias mais interessantes da WWDC de 2019 foi a introdução do SwiftUI, que muda completamente a forma como as telas são criadas em Swift, colocando-se no meio da discussão entre view code e storyboards, e aproveitando o melhor dos dois mundos, com sua sintaxe declarativa e live preview. 

image2

Mas isso não é tudo.

O impacto do SwiftUI não está limitado apenas a forma como criamos telas. Ele também muda a forma como nós arquitetamos nosso código. 

Então, nesse primeiro artigo, eu irei mostrar como migrar para SwiftUI, um app desenvolvido utilizando UIKit e arquitetura MVC. E, então, darei algumas dicas sobre como evitar o problema mais comum dessa arquitetura: o Massive View Controller (view controller gigante). 

Então, vamos começar.

  • • • 

MVC 

Se você é um desenvolvedor iOS, provavelmente já ouviu falar sobre MVC, a arquitetura oficial da Apple. Mas, se você nunca ouviu, não se preocupe. 

No MVC, temos os seguintes componentes: 

  • Model: Realiza requisições de rede, carrega informações, faz o parser das respostas, persiste configurações.
  • View: Responsável por renderizar telas e receber ações do usuário. Quando um usuário aperta um botão, por exemplo, a view recebe essa ação e informa o ViewController. 
  • ViewController: Recebe ações dos usuários a partir da view e lida com elas, chamando a camada de Model para fazer o que for necessário e, então, atualizando a view e reiniciando o ciclo. 

image3

Mas, quando falamos de SwiftUI, temos uma grande mudança. 

Não tem mais ViewController!

  • • • 

Substituindo o ViewController 

Então, como podemos ter alguma forma de MVC se não temos mais um ViewController? 

Bem, uma outra mudança introduzida com o SwiftUI é o uso de Data Binding e o framework Combine, seguindo uma abordagem reativa. E podemos utilizar o conceito de Store, um objeto que armazena os dados e estados do app e é observado pela view, de forma que, toda vez que algum dado é alterado, ela é atualizada automaticamente. 

Na arquitetura proposta, nós iremos substituir o UIViewController do MVC por uma Store, resultando em algo assim: 

image4

Vamos ver um pouco de código agora!

Primeiro, criamos um simples objeto Item com algumas propriedades, e que pertence à camada de modelo.

image5

Depois, um ItemDataProvider (fornecedor de dados sobre itens), também pertencente à camada de modelo e responsável por realizar uma requisição de internet para carregar os itens. Nesse exemplo, nós utilizamos o framework combine para obter uma resposta assíncrona. 

image6

Um Publisher (publicador) é basicamente um objeto que publica novos valores para os subscribers (assinantes) toda vez que algum dado é alterado ou novos dados ficam disponíveis. E ele tem dois tipos associados: o tipo do dado a ser retornado (um array de itens no exemplo) e o tipo de erro que pode ocorrer (no exemplo, o publicador nunca lança um erro, porque substitui os erros por um array vazio de itens). 

Nós também temos o ItemListStore (armazenador de lista de itens), que chama o fornecedor de dados para carregar as informações, assim como o ViewController deveria fazer na arquitetura MVC. Depois, quando ele recebe o array de itens, ele atualiza a propriedade local chamada items. 

Capturwwwe

 

 Uma grande diferença aqui é que a Store não precisa informar a view explicitamente para ela ser recarregada. Como a propriedade Items é marcada com a anotação @Published, no momento em que ela é atualizada as alterações são publicadas utilizando o framework combine e a view é recarregada.

E, finalmente, temos a view, que possui uma referência para a Store e chama o método fetchItems quando é apresentada. 

Capturue

 

Com o exemplo acima, nós cobrimos apenas o cenário de busca de dados a serem exibidos quando a view é apresentada. No caso do usuário fazer alguma interação com a tela, o fluxo seria basicamente o mesmo: 

A view recebe a ação e informa a Store. A store chama a camada de modelo (um fornecedor de dados, um serviço, um gerenciador de persistência ou qualquer outra coisa) para realizar alguma ação, e depois atualiza suas propriedades, fazendo a view ser recarregada.

E, com relação a navegação, nós substituímos o uso de segues no storyboard pelo uso de NavigationLink, como pode ser observado no exemplo. 

  • • •

Evitando uma store gigante 

Um dos maiores problemas com MVC é o conhecido problema do Massive View Controller (view controller gigante). Em qualquer busca que você fizer sobre essa arquitetura, provavelmente irá encontrar alguém brincando que é isso que a sigla MVC significa. 

Mas recentemente eu encontrei a seguinte afirmação nesse artigo (Much ado about iOS app architecture): 

Por que cada um dos artigos descrevendo qualquer uma dessas novas arquiteturas começa com uma variante de “O MVC da Apple leva para um M(assive)VC”.

 Quando eu leio isso, eu penso: aqui tem outro aspirante que nunca se preocupou em aprender como delegar apropriadamente. Que nunca aprendeu como usar container controllers e compartimentar funcionalidades em embedded controllers. 

PS: ninguém está te forçando a implementar múltiplos DataSources em um Controller. A iniciar chamadas de rede no viewDidLoad. A parsear JSONs no UIViewController. A amarrar views com singletons. Se você faz isso, a culpa é sua; não culpe o MVC. 

Essa é uma afirmação bastante dura, mas também interessante. Ela me incomodou, assim como provavelmente está te incomodando, e me fez pensar. 

No final, eu cheguei à conclusão de que MVC é um arquitetura conceitualmente simples, fácil para os iniciantes aprenderem e começarem a produzir algo. Porém, também é muito fácil de produzir uma bagunça. 

Para fazer o MVC funcionar bem, você precisa ser bastante disciplinado e ter um conhecimento muito bom sobre como decompor suas views e dividir responsabilidades, porque não está muito claro nos três componentes principais (model, view and controller) como fazer isso. 

Outras arquiteturas, como VIPER e Clean Swift, possuem mais componentes, com responsabilidades mais claras e específicas. O que pode ser mais difícil de entender no começo, mas depois de um tempo, tem uma maior probabilidade de resultar em um código bem organizado, sem arquivos muito extensos ou com muitas responsabilidades diferentes. 

Para fazer as coisas funcionarem com MVC, você precisa saber como organizar a camada de modelo, tendo diferentes sub-componentes para lidar com chamadas de rede, parser, persistência, cálculos e lógicas de negócio. Nenhuma dessas coisas deve estar no seu ViewController. 

Você também precisa prestar atenção a sua view. Ela deve ser responsável apenas por coisas relacionadas a apresentação, e não deveria receber objetos da camada de modelo para se configurar. 

E, finalmente, você precisa saber como organizar seu view controller e entender que uma tela não necessariamente significa um único view controller. Você pode (e em alguns casos deve) dividir suas telas em views menores com container view controllers. Dessa forma, você não irá acabar com um único e gigante view controller que atua como delegate e data source de várias views ao mesmo tempo. Esse artigo (A Better MVC) mostra uma forma interessante de fazer isso.

  • • • 

Retornando agora para o SwiftUI, nós precisamos aplicar a mesma disciplina. O model deve ser organizado da mesma forma, e nossa view deve ser decomposta em views menores e organizadas, com stores também organizadas e pequenas. 

Se algum arquivo do seu projeto está ficando muito grande, provavelmente ele está fazendo muitas coisas e você deve dividi-lo em componentes menores com responsabilidade única. 

Até mesmo no nosso exemplo anterior que era bem simples, a view está fazendo duas coisas. Ela está renderizando a lista de itens e lidando com ações, chamando a store para buscar as informações quando a view é apresentada. 

  • • • 

Decompondo nossa view 

Para resolver isso, nós podemos dividir a view em duas da seguinte forma: 

image9

Note que também foi adicionada a opção de remover um item, e que isso foi bem simples de fazer. 

Um último ponto é que, em SwiftUI, as views são structs e usam composição, em vez de serem classes e usarem herança como no UIKit. Como resultado, as views são bem leves e é altamente recomendado criar tantas subviews simples e reutilizáveis, quanto for possível. 

Então, como último exemplo, imagine que você tem uma tela do seu app para um carrinho de compras, e que você deseja apresentar as seguintes informações:

  • Lista de itens
  • Editar quantidade dos itens 
  • Botão para finalizar a compra. 
  • Carrossel de itens sugeridos 
  • Loading view enquanto carrega as informações 
  • Empty state pra quando não tem itens no carrinho  
  • Tela de erro caso ocorra algum problema. 

Isso é muita informação pra ser controlada e, se você colocar tudo isso em uma única view, com um único controller ou uma única store, seu código vai ficar muito desorganizado. Para evitar isso, você pode dividir essa tela nas seguintes views: 

  • ShoppingCartView (para a tela inteira) 
  • SuggestedItemsCarouselView (para o carrossel de itens) 
  • CarouselItemView (para um item do carrossel) 
  • CartItemsListView (para lista de itens do carrinho) 
  • CartItemView (para um item do carrinho)
  • QuantityEditionView (para editar a quantidade) 
  • FinishSaleButton (para finalizar a venda) 
  • LoadingView (para tela de carregamento) 
  • ErrorView (para tela de erro) 
  • EmptyStateView (para o carrinho vazio)

E você também pode ter stores diferentes, como: SuggestedItemsStore, CartItemStore e PurchaseStore. 

Dessa forma, você terá componentes muito menores e mais claros.

Charântola

Charântola

iOS developer. Interested in code architecture, algorithms, new technologies, movies and series.

Deixe um comentário

Categorias

Posts relacionados

Siga-nos

Baixe nosso e-book!

%d blogueiros gostam disto: