Como escrever um excelente feed de notícias do VKontakte em 20 horas

Olá pessoal! Recentemente, houve um concurso do VKontakte Mobile Challenge, e meu trabalho ganhou um prêmio. Nas instruções da segunda etapa, foi necessário desenvolver um feed de notícias para dispositivos móveis, e os principais critérios de avaliação foram rolagem suave e pós-carregamento. Quando participei, decidi que, independentemente do resultado final, tentarei escrever um artigo sobre a abordagem da implementação da fita e sobre minhas emoções e sentimentos durante o concurso. O que eu fiz. Sob o gato, dicas e truques para desenvolver feeds de notícias no modo de contar histórias.



Sobre o concurso


Antes de tudo, vale a pena contar um pouco sobre a competição. Como eu sei, o VKontakte organiza anualmente esses eventos para desenvolvedores móveis. Eu próprio já participei em 2012 e 2013. As tarefas foram o desenvolvimento de filtros de bate-papo e imagem, respectivamente. Em 2013, consegui chegar à rodada final e ganhar 100.000 rublos, o que me pareceu uma quantia muito boa por 4 dias de trabalho interessante.

E, vendo os anúncios em uma rede social, decidi que precisava tentar, porque o interessante é provar a si mesmo que você ainda é capaz de escrever código de alta qualidade em um tempo muito curto).
Havia duas etapas na competição: na primeira, foi proposto passar em um teste de 30 perguntas, resolver dois problemas das Olimpíadas e um de alta qualidade (ou seja, uma solução é um conjunto de pensamentos na forma de texto). Na segunda rodada de mais de 1000 pessoas, apenas 112 foram aprovadas (estamos falando apenas do iOS, no Android, valores um pouco mais baixos) e o estágio principal começou - o desenvolvimento de aplicativos.

Na tarefa, era necessário criar um feed de notícias, escrever a integração com a API VK, o feed deveria ter sido muito bom em todos os dispositivos, havia certos requisitos para a exibição de conteúdo (postagens com imagens e carrossel, contadores de gostos, compartilhamentos, número de visualizações e a capacidade de minimizar e implantar a postagem sob certas condições).

Os layouts foram dispostos em Figma. Essa é uma boa escolha para a competição - devido ao número de usuários ativos, a competição é sentida imediatamente (e até nocauteada no sábado, porque o limite simultâneo de 50 conexões foi excedido).

Em geral, a organização da competição foi tranquila, exceto por alguns pontos: imediatamente após a publicação da tarefa, o link para os layouts acabou sendo quebrado; então, como eu disse acima, no sábado, a entrada para Figma foi bloqueada (esse problema foi reparado muito rapidamente), bem, após o evento, demorou um pouco esperar resultados. Mas todo o negativo suaviza um post semelhante:


Plano e Estratégia da Vitória


Um bloco muito importante e interessante se você participa de competições ou gostaria de tentar. Antes de participar, recomendo a todos que pensem em qual objetivo você está buscando e em qual resultado deseja sair. Antes de iniciar o concurso, decidi por mim mesmo que queria vencê-lo (ou seja, receber o prêmio). E para fazer isso, você precisa de uma estratégia:


A seguir, é apresentada a fórmula para uma estratégia vencedora (parece-me universal e simples, mas tenho certeza de que a maioria dos participantes não a segue):

  • Leia a tarefa com muito cuidado . Depois de algumas horas, leia-o novamente (entrei em uma situação em que não percebi tarefas pela primeira vez). E algumas horas antes da entrega mais uma vez, por precaução.
  • Componha perguntas e faça-as aos organizadores (geralmente os requisitos para a tarefa não levam em consideração muitas situações ou são expostos muito superficialmente). Fiz exatamente isso, obtendo respostas no estilo de "sua escolha".
  • Faça um plano de projeto. Sim, a participação em uma competição ou hackathon é um projeto. O projeto tem uma meta, cronograma, recursos e orçamento. O tempo é estritamente limitado e é impossível mover o fim da mudança. Pessoas adicionais não podem ser convidadas - a competição não é uma competição por equipes. Tudo que você tem é seu próprio povo. / horas. Determine imediatamente quanto você está disposto a gastar (pense bem quanto vai dormir, comida, castração, pausas e, sim, sua produtividade cairá devido ao cansaço e aos nervos de um sentimento de impotência diante de um período inexoravelmente próximo).
  • O plano deve ter tarefas, suas prioridades e pesos (usei estimativas no formato 1, 2, 4, 8). A avaliação inclui a duração, a falta de vontade de cumpri-la e os riscos potenciais (complexidade, condições incompreensíveis, nenhuma experiência em tal desenvolvimento, etc.). Em seguida, você elabora um plano detalhado (ou tarefas do roteiro) - primeiro o mais importante e complexo (deixe fácil e compreensível no final).
  • Escolha alguns intervalos ou marcos grandes (os dias são ótimos - tivemos três dias). E, ao analisar cada uma delas, analise seu plano de trabalho, decomponha as tarefas, classifique-as e, o mais importante, cruze as que você já fez com prazer.
    Agora, o conselho (fiz exatamente isso): observe atentamente como a tarefa é formulada, se ela contém as palavras "obrigatório" e "adicionalmente". E quais são os critérios de avaliação para os itens do "adicional", ou seja, se eles substituirão sua falta de requisitos implementados na seção "obrigatório". Eu duvido. Portanto, concentre-se na seção "obrigatório". Para mim, geralmente passei deliberadamente a seção "avançado" e nem pretendi iniciá-la.
  • Ao implementar, tenha cuidado com os detalhes . Por exemplo, se a tarefa tiver muitos formulários de interface, faça-os da maneira que são exibidos nos layouts. Preste atenção aos recuos, fontes, sombras, espaçamento entre linhas, etc. Isso certamente adicionará pontos de precisão e consistência. A propósito, no Figma, diferentemente do Zeplin, há uma exportação muito ampla de configurações, por exemplo, você pode obter uma boa NSAttributedString.

A missão foi aberta na noite de quinta a sexta-feira. Sexta-feira é um dia útil e não havia oportunidade e desejo de participar de uma competição no trabalho. Portanto, depois de voltar do trabalho na sexta à noite, comecei a planejar, avaliar e calcular quanto tempo real posso gastar.

O plano inicial era o seguinte:
DiaTempoAs tarefas
Sexta-feira4 horasAutorização, obtendo dados para o perfil e fita.
Sábado9 horasO protótipo da fita com a exibição de quadrados vermelhos de diferentes alturas, carregando-os por cima, puxando para atualizar e carregando infinitamente na história, preparando todos os modelos e serviços (trabalhando com a API, solicitando cache, cache de imagens).
Domingo9 horasSaída de dados reais (texto, imagens únicas e carrosséis), contadores de curtidas, visualizações e ajuste final por layouts.

Técnica de implementação de fita


Viva! Senhores, desenvolvedores, você encontrou o bloco certo. Aqui falaremos sobre a implementação da parte principal da tarefa - o feed de notícias.

Em geral, se desativarmos, o feed de notícias é apenas uma implementação aplicada; em geral, falaremos sobre a criação de uma lista de entidades heterogêneas (ou seja, cada uma pode ter sua própria exibição, o que requer cálculos adicionais e sua própria altura, que podem mudar dinamicamente mesmo após a exibição da postagem) , além disso, a lista pode ser atualizada por pull-to-refresh e carregar infinitamente dados a partir de baixo.

Foi o problema geral que decidi resolver em primeiro lugar. O primeiro passo é analisar as soluções existentes. Você precisa se concentrar no mais forte, então eu escolhi 3 aplicativos para comparação: Vkontakte, Facebook, Instagram.

Então, eu queria realizar um estudo dos 6 problemas mais agudos e críticos:

  1. Puxar para atualizar (adicionar ao topo da lista)
  2. Carregamento suave do histórico (adicione ao final da lista)
  3. Rolagem rápida (alternadamente com os dedos, aceleramos a fita tanto quanto a força de atrito permite)
  4. Role para cima (acumule uma grande história e clique na barra de status)
  5. Existe uma divulgação dinâmica (aumento) da postagem e qual é a animação
  6. Como a fita funciona no modo de perda quase completa de pacotes (Desenvolvedor -> Condicionador de Link de Rede -> Rede Muito Ruim)


Em geral, todos os aplicativos se comportam bem, mas ainda notei alguns problemas.
Veja, por exemplo, como o pull-to-refresh se comporta no VKontakte se você não soltar o dedo durante a atualização e puxe a fita para cima com cuidado (veja o gif à esquerda).

Você também vê esse salto?

Instagram e Facebook não mostraram esse comportamento.

E também há uma diferença notável na divulgação posterior. Para o Facebook e o Instagram, isso acontece com uma animação suave e o VKontakte simplesmente atualiza o tamanho clicando em.



Portanto, nossa tarefa é fazer uma rolagem suave, fazer o download de postagens e também revelar com animação.

O primeiro passo é escolher um conceito e criar um protótipo em quadrados vermelhos (eu me pergunto por que sempre escolho intuitivamente uma cor vermelha para protótipos. É esse o caso de todos?).
Minha principal idéia para melhorar a produtividade foi abandonar todos os sinos e assobios que a Apple introduziu por muitos anos e literalmente reverter para o desenvolvimento do iOS 3. E isso significa:

  • Recusa do AutoLayout (sim, é lento, não acredito - posso provar nos comentários)
  • Recusa em calcular automaticamente a altura das células da tabela

Como resultado, o seguinte conceito foi escolhido:



Vamos ficar um pouco mais detalhados.

No segmento principal, atualizamos a interface, processamos ações do usuário (role para baixo e puxe para atualizar, clique em uma postagem para aumentá-la) e acionamos um modelo de serviço para receber e preparar dados.

Chamada de API . Tudo é simples aqui - NSURLSession com NSURLCache configurado. É importante que, ao carregar o histórico para baixo, utilizemos o cache e, ao puxar para atualizar, o desabilitemos. Exatamente esse comportamento que espiei no VK e no Facebook.

Analisando e Criando Modelos . Aqui está a lógica para processar uma solicitação específica, lançando erros e retornando modelos de transporte com dados.

Cálculo de modelos de apresentação . A etapa mais importante para otimizar o desempenho. Aqui, os modelos de transporte são transformados em entidades com o postfix do PostModel. O ViewModel armazena dados completamente preparados para exibição - AttributedString, altura calculada da célula (para 2 estados: recolhidos e expandidos), nome completo como uma sequência, sequência de datas (já convertida de DateFormatter).

Somente depois disso os dados retornam ao fluxo principal. A implementação dessa lógica no Swift é muito conveniente e simples. Nós criamos a estrutura do ViewModel. As estruturas quando transferidas para um novo fluxo são copiadas.

Excelente conceito pronto, agora vamos falar sobre o próprio mecanismo de saída.

Primeiro, você tinha que escolher no que implementar a fita - UITableView ou UICollectionView (definitivamente não haveria tempo suficiente alocado para sua implementação). Obviamente, o UITableView é adequado para listar a saída, mas fiquei muito preocupado se haveria problemas com o aumento da lista da parte superior, inferior e também com o aumento da célula de conteúdo. Portanto, decidi ir do simples ao complexo - ou seja, se o UITableView não tiver problemas, nós o deixamos.

A primeira coisa que decidi foi optar por puxar para atualizar. Para implementar esse padrão, existe um UIRefreshControl. Uma vez, cerca de 5 a 6 anos atrás, escrevi minha implementação usando o UIActivityIndicator e alterando o contentInset da tabela. Então, por favor, não faça isso agora! O UIRefreshControl possui uma interface compacta conveniente e utiliza uma nuvem de muletas, que você certamente precisará fazer. Usá-lo é muito simples:



No entanto, ao usá-lo, fica claro de onde crescem os problemas mostrados acima para o cliente VK. Parece que eles existem no próprio componente. Eu rapidamente tentei procurar o que poderia ser o motivo e quais são as soluções.

As dicas da Internet dizem (ou não):

  • verificar ao chamar endRefreshing se o controle agora está atualizado (isRefreshing)
  • use exatamente UITableViewController (porque nesse caso o método privado mágico é chamado)

Eu tentei isso e aquilo - não tive nenhum efeito positivo. Fiquei chateado, mas decidi não perder tempo e seguir em frente. A propósito, se alguém souber como superar esse problema - escreva nos comentários.

O próximo passo é implementar o carregamento de dados a partir de baixo.

No começo, não fiquei muito legal - ao carregar por baixo, havia um atraso terrível (pulei o contentSize da tabela aparentemente):


E isso é o inferno :-) Com esse resultado, você definitivamente não pode vencer. Mas uma pesquisa rápida me deu uma pista incrível que eu esqueci:



E pronto:


Resta decidir como animar a altura da célula.

A primeira ideia que veio à mente foi executar as células visíveis e aumentar a altura no bloco de animação. Mas mesmo na fase de análise, essa idéia deve ser descartada - o problema é que será necessário sincronizar a altura especificada no UITableViewDataSource e fazer algo com o contentSize (e isso é altamente recomendado pela Apple).

O segundo pensamento que veio à mente se mostrou correto - o UITableView possui métodos de inserção / recarga / exclusão que podem ser executados no bloco de animação:



Não esqueça que em heightForRowAt também precisamos adicionar uma nova altura:



Tudo parece estar, mas não exatamente! Na animação da implantação do post, havia um ponto sutil. Procure o texto no Intagram ou no Facebook - ele aparece sem problemas imediatamente com o aumento da altura. O que fazer Renderizar linha por linha? Se você atingir o nível de NSTextContainer, talvez uma oportunidade semelhante seja exibida. Mas me pareceu que não era uma má idéia (por um tempo) exibir todo o texto de uma só vez. Basta definir clipToBounds como superView, no qual o UILabel estará localizado, exibindo nosso texto. E a abordagem funcionou! Oh, essa animação me inspirou muito e um segundo vento se abriu. Afinal, não existe essa animação no cliente nativo do VK. Então ela deve adicionar chances de ganhar :-)

Os detalhes restantes na implementação da fita não são tão interessantes. Mas você pode perguntar a eles nos comentários. E não há nada errado em definir o código. Aqui está - github.com/katleta3000/vkmobilechallenge . Sinto muito por não estar penteado e há Números Mágicos em alguns lugares (mas este é um concurso de velocidade, tive que sacrificar alguma coisa). A propósito, você pode pular em commits lá (os nomes são bem claros).

O resultado pode ser visto aqui - www.youtube.com/watch?v=Md8YiJxSW1M&feature=youtu.be (é claro que a qualidade está muito perdida, mas melhor do que no gif para demonstrar uma rolagem suave)


Estatísticas, Resultados, Conclusões


O tempo todo o concurso funcionava em um cronômetro. Saiu 20 horas e meia de tempo de codificação puro. O tempo de rastreamento ajudou em duas coisas: como você se lembra, havia estimativas em unidades abstratas; assim, no meio do último dia, já havia boas estatísticas de rastreamento e foi possível planejar as tarefas finais restantes com muito mais precisão. E, em segundo lugar, foi possível identificar e provar o padrão de "concentração em uma sessão". Cada dimensão é uma nova iteração, portanto, eu tinha 68 iterações para escrever código. Se você fizer uma média, você terá 18,5 minutos. Mas, de fato, no primeiro dia de iteração, houve uma média de 25 minutos e, no final do segundo dia, 7 minutos cada :-) Você começa a enlouquecer, fica muito nervoso, se cansa e o desempenho diminui. Esses dados ajudarão bem na próxima vez.

Pessoalmente, usei o programa Hourly (você pode baixá-lo e experimentá-lo) - ele simplesmente resolve a tarefa necessária (e até mesmo o desenvolve):

Dos bons bônus - para cada quebra-cabeça fechado, uma tela de incentivo é exibida, como a seguir:


É engraçado que foi na conclusão da tarefa "VK Mobile Challenge" que a tela a seguir me apareceu:


E sim! E assim aconteceu :-) Continuamos com os resultados.

Não vamos puxar Peach para o luxuoso. Peach, que não sabia, é o nome do gato - o personagem principal de VK. Vale-presente muito legal:


Tomei o quarto lugar e consegui 175k rublos. (Sim, e você provavelmente já gravou a foto em Habra-kat). E sim, certamente estou satisfeito. Atingi meu objetivo :-) E isso é, sem dúvida, incrivelmente agradável.

No final, gostaria de agradecer à vkontakte - afinal, a competição foi legal e bem organizada. E recomendo a todos os leitores que participem de desafios e hackathons - essa é uma ótima maneira de desafiar a si mesmo e competir com os principais desenvolvedores do mundo (bem, ou pelo menos com a comunidade de língua russa).

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


All Articles