Evolução da arquitetura móvel do Reddit



Este é o primeiro dos artigos em que falamos sobre a arquitetura do aplicativo Reddit para iOS. Aqui estamos falando de funcionalidades que funcionam mais perto da interface do usuário. Em particular, a transição para a arquitetura Model-View-Presenter (MVP). As vantagens dessa refatoração:

  • Melhorar a flexibilidade, a clareza e a manutenção do código para dar suporte ao crescimento futuro e acelerar iterações.
  • Desempenho de rolagem aumentado em 1,58 vezes.
  • Teste de unidade estimulante. O número de testes aumentou de alguns para mais de 200.

Abaixo está um diagrama visual da nossa arquitetura em camadas. O primeiro artigo se concentrará nos níveis de exibição e apresentador.


A melhor visão da nossa arquitetura em camadas

Pré-requisitos para mudança


Mais de um ano atrás, publicamos o artigo “Construindo uma faixa de opções em um aplicativo iOS do Reddit” . Ele discutiu como gerar um feed produtivo e expansível com uma taxa de sessão extraordinária de 99,95% sem travar. Explicamos como usar a arquitetura Model-View-Controller (MVC) e criar abstrações para dados de paginação.

Agora o Reddit cresceu e continua a crescer como organização e como serviço. Consequentemente, os requisitos para o aplicativo iOS do Reddit aumentaram. Ele deve suportar mais solicitações de recursos, loops iterativos mais rápidos e padrões de qualidade mais altos. A equipe de desenvolvimento cresceu de três para mais de vinte pessoas. A arquitetura MVC original dificilmente atende a esses requisitos, portanto, mudanças na arquitetura tiveram que ser feitas.

A essência do problema


Com o tempo, o código perdeu flexibilidade e clareza. Na comunidade de desenvolvedores do iOS, a abreviação MVC é muitas vezes decifrada como Massive View Controller, porque os controladores de visualização costumam inchar para objetos divinos com mais de mil linhas. Apesar de todos os nossos esforços, surgiu um problema: a hierarquia da herança tornou-se desconfortavelmente profunda e os controladores começaram a se transformar em objetos divinos obscuros que resistem à mudança.

Colocamos o último prego no caixão do MVC quando decidimos alterar o nível de apresentação da fita. A audiência do aplicativo Reddit está crescendo, portanto o desempenho de rolagem diminuiu com frequência de 60 FPS para 45–55 FPS. Isso significa que você precisa reescrever a camada de apresentação da fita enquanto mantém a implementação original. Mas na arquitetura MVC existente, não foi possível reescrever a camada de apresentação da fita sem duplicar milhares de linhas de código.

Além disso, muitas partes da base de código são difíceis de testar. O código está na classe testada da camada de apresentação e as dependências geralmente estão sozinhas (singleton) ou codificadas na própria classe. Queríamos perceber a possibilidade de testes normais.

Outras tarefas definidas para refatoração: o número de falhas deve permanecer baixo, a nova arquitetura deve estabelecer as bases para o crescimento futuro e não interferir nas funções que dependem da infraestrutura existente. Ou seja, mudanças evolutivas, não revolucionárias, são necessárias.

Transição para MVP


Decidimos que para resolver os problemas acima, é necessária uma nova versão do aplicativo. Depois de considerar várias opções, decidimos usar a arquitetura Model-View-Presenter (MVP). O MVP atende a todos esses critérios e é uma arquitetura bem conhecida e documentada, tornando mais fácil treinar engenheiros. Ele também mantém o conceito de "visualizar modelos". Se necessário, no Presenter, você pode criar objetos de modelo de exibição com base no princípio de responsabilidade exclusiva - e usá-los para expandir nossas visualizações.


Gráfico de exibição de modelo e apresentador

Livrar-se do Massive View Controller


Para aplicativos iOS, é aceito que os objetos de exibição sejam subclasses do UIView, os objetos do controlador sejam subclasses do UIViewController e os objetos de modelo sejam objetos simples de ol. Como o nome UIViewController implica, aqui a visualização e o controlador são combinados em um objeto. Ou seja, o modelo MVC no iOS geralmente perde suas vantagens devido à conexão estreita entre o nível de apresentação e o controlador. Curiosamente, a própria Apple reconhece essa conexão .


Freqüentemente, a arquitetura Model-View-Controller para iOS se transforma em

Na arquitetura MVP, levamos em conta esse conceito e o formalizamos, considerando o UIViewController realmente como um objeto explícito da camada de apresentação. O conceito de tratar um UIViewController como um objeto de apresentação com um nome ruim se tornou popular nos últimos anos.

Portanto, removemos toda lógica estranha em nossos UIViewControllers. Em seguida, atribuímos o Presenter ao intermediário entre a visualização e o modelo. Nesta nova função, ele não tem conhecimento de objetos de exibição como o UIViewController. Observe que o Presenter interage com a visualização por meio de uma interface. Teoricamente, você pode alterar a implementação de uma exibição para NSViewController (para MacOS) etc.


Facilitamos o ViewController introduzindo o Presenter e compartilhando responsabilidades

Pondering Opções de MVP


Como você pode ver no diagrama do MVP, a arquitetura saiu muito semelhante ao MVC. De fato, existem mais semelhanças do que diferenças. Essa arquitetura simplesmente ajuda a estabelecer a separação correta do código de apresentação e da lógica de negócios que o MVC procura. De fato, todos os derivados da arquitetura do MV (x), como MVP, MVVM, MVAdapter e outros, são simplesmente versões diferentes do mesmo conceito.

Pode-se perguntar por que abandonamos completamente o MVC. De fato, a Apple descreve diferentes tipos de controladores : para modelos, intermediários e coordenação. Honestamente, talvez possamos substituir nosso apresentador por outro controlador. Mas eles decidiram não fazer isso, porque a maioria dos desenvolvedores de iOS, por algum motivo, acreditava que o UIViewController é sinônimo de controlador. Usando a palavra Presenter, meio que sinalizamos que esse objeto é significativamente diferente de um controlador convencional com um determinado conjunto de funções e propriedades.

Melhorando a flexibilidade, sustentabilidade e clareza


"Preferir composição sobre herança" é um mantra bem conhecido na programação de objetos. Com a herança, você precisa prever o futuro e criar uma enorme taxonomia de objetos. Mas se sua hierarquia de herança construída "idealmente" começar a desmoronar devido a mudanças imprevistas, é difícil modificar essa estrutura rígida. Na composição, os objetos são criados a partir de outros objetos e delegam trabalho a eles. Isso é útil porque é fácil alterar o comportamento de um objeto em tempo de execução, simplesmente alterando os objetos nos quais ele consiste. Esses objetos compostos são ainda mais compreensíveis, pois o código é forçado a sair da hierarquia de herança para uma abstração orientada para uma tarefa específica.

Essa composicionalidade é uma das principais vantagens que a arquitetura MVP nos deu. Agora você pode alterar o comportamento do controlador simplesmente alterando a composição de um determinado Apresentador. Agora estamos menos preocupados em decifrar a estrutura de herança complexa e rígida. Por fim, os controladores de exibição e os objetos do Presenter são mais fáceis de entender porque eles têm um conjunto mais claro de tarefas.

Ao introduzir o Presenter e mover parte da lógica do controlador de exibição para lá, simplificamos a hierarquia de herança do controlador. A figura abaixo mostra que conseguimos remover a classe GalleryFeedViewController, pois colocamos toda essa lógica no Presenter. Como já discutido, essa hierarquia de herança é mais fácil de entender e menos rígida.


Simplifique a hierarquia de herança através da composição

Mudança livre na implementação da camada de apresentação


Como discutido anteriormente, o desempenho da rolagem de fita começou a diminuir de 60 FPS para 45–55 FPS. Portanto, para a camada de apresentação da fita, decidimos usar Textura . É uma plataforma baseada na Apple UIKit de código aberto que melhora o desempenho da interface por meio de pré-processamento no encadeamento em segundo plano. Na arquitetura MVC anterior, não era possível alterar a implementação do nível de apresentação sem muita duplicação de código.


Antes de implementar o MVP, era necessário duplicar código estranho no ViewController que não estava relacionado ao View (laranja)

A nova arquitetura MVP permitiu a introdução do suporte de textura, em vez de reescrever as coisas do zero. Acabamos de colocar toda a lógica não vista na classe genérica do Presenter. Em seguida, eles escreveram uma nova implementação da camada de apresentação de textura c e reutilizaram o código do Presenter. Isso deu suporte para ambas as implementações do View até a hora de lançar confortavelmente a fita com o Texture para todos os usuários.


Após a implementação do MVP: código sem visualização movido para o Presenter compartilhado

Qual é o resultado? O diagrama abaixo mostra um aumento no desempenho do rolo de fita. Queríamos ficar em torno de 60 FPS para obter uma rolagem absolutamente suave.



Teste de unidade


Obviamente, implementamos testes de unidade não apenas por causa do MVP, mas foi um fator importante. Em particular, a arquitetura MVP expandiu a área de teste, movendo o código para um nível em que é mais fácil verificar. Um efeito colateral é que os níveis de exibição se tornaram mais simples - e, portanto, precisam ser testados com menos frequência.


Aumentando a área de teste após mover o código não visualizado para fora desta camada

Os testes de unidade aprimoraram o suporte à base de código: permitem fazer alterações com mais confiança e ajudam a entender qual deve ser o comportamento correto. Eles também tornam o código mais flexível e compreensível, porque incentivam métodos como injeção de dependência, composição e programação de abstração. O número de testes de unidade aumentou de alguns para mais de 200.

Análise crítica do MVP no Reddit


Embora a mudança para o MVP tenha ajudado muito, ainda há algumas coisas a considerar.

A transição da fita para o Texture causou novos problemas com os threads. O aplicativo não oferece suporte inicialmente à implementação assíncrona do View. Ou seja, os erros aparecem inevitavelmente no caso de uma incompatibilidade entre o estado de exibição e o estado do aplicativo. Por exemplo, uma exibição em fita pode ter N registros. E no encadeamento em segundo plano, o estado do aplicativo mudou silenciosamente - e agora contém menos de N mensagens. Se a discrepância não for resolvida, o aplicativo simplesmente trava quando o View tenta exibir a enésima postagem no fluxo.

O mais difícil de corrigir erros com threads. Eles são difíceis de reproduzir, por isso são difíceis de depurar. Eu tive que mudar a lógica da solicitação e receber dados para visualizar o feed. Em particular, implementamos "proteção" com a proibição de qualquer alteração na fonte de dados quando a Visualização da fita sofre algumas alterações. Essa e outras pequenas correções reduziram o número de erros associados ao processamento de streaming. No entanto, o multithreading assíncrono ainda pode ser aprimorado.

Em segundo lugar, a camada Presenter representa um "passo" extra no pipeline. Esta etapa tem um preço em termos de aumento da complexidade do código e redução do desempenho. Às vezes, você apenas deseja executar essa lógica no UIViewController por capricho ou porque está acostumado a fazê-lo. Na pior das hipóteses, você encontrará que o Presenter está presente simplesmente como uma entidade, sem nenhuma lógica significativa. Em tal situação, o Presenter não parece justificar sua existência.


Às vezes, você pode alternar de uma camada Visualizar para uma camada RedditCore sem o Presenter

De fato, nosso aplicativo não é totalmente convertido na arquitetura MVP. Primeiro, converter cada UIViewController individual em um apresentador consumirá muito tempo - e não será evolutivo. Em segundo lugar, como mencionado no parágrafo anterior, às vezes o Presenter simplesmente não é necessário. Como descobrimos em nosso trabalho de implementação do Texture for Ribbon, o Presenter é ótimo para facilitar o MVC maciço ou para implementar o View com comportamento variável, ou se você tiver uma lógica complexa que precisa ser verificada. Mas, às vezes, o UIViewController é tão simples que o Presenter não faz sentido. Então é opcional. O apresentador deve ser implementado apenas se necessário.

Resumo e planos futuros


A refatoração da arquitetura MVP no aplicativo iOS do Reddit ajudou a resolver muitas das tarefas. Com a introdução da camada Presenter, desenvolvemos gradualmente a arquitetura do aplicativo para dar suporte à nova implementação da camada de apresentação, sem interromper outras funções. O código ficou mais claro ao facilitar o "MVC maciço" - transferindo lógica estranha para a camada Presenter. Também demos aos desenvolvedores a capacidade de interagir mais rapidamente e implantar novos recursos. E melhorou significativamente os testes.

Diante de tudo isso, ainda há um longo caminho a percorrer. Continuamos a criar objetos do Presenter e melhorá-los. Precisamos continuar a mover lógica estranha de UIViewControllers para o nível do Presenter. Também é necessário que todos os apresentadores estejam melhor alinhados com o princípio de responsabilidade exclusiva. No final, o aplicativo e a arquitetura estão em constante evolução.

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


All Articles