
Existem vários truques e truques que ajudam a otimizar o trabalho dos aplicativos iOS, quando uma tarefa deve ser concluída em 16,67 milissegundos. Nós dizemos como descarregar o thread principal e quais ferramentas são mais adequadas para rastrear a pilha de chamadas nele.
“Pessoal, vamos imaginar que você pode reduzir o tempo de inicialização em 10 segundos. Multiplicando isso por 5 milhões de usuários, teremos 50 milhões de segundos por dia. Em um ano, isso equivalerá a cerca de dez vidas humanas. Portanto, se você fizer o download inicial 10 segundos mais rápido, salvará várias dezenas de vidas. Realmente vale a pena, não é?
Steve Jobs sobre desempenho (tempo de inicialização do computador Apple II).
O artigo é baseado em um relatório do desenvolvedor do iOS da Fyusion, Luc Parham, que falou na MBLT DEV International Mobile Developers Conference no ano passado.
O MBLT DEV 2018 será realizado em Moscou no dia 28 de setembro. Os ingressos são os mais baratos no momento. Por tradição, enquanto o Comitê de Programa seleciona relatórios, você pode comprar ingressos antecipados para o konf. Aproveite esta oportunidade agora. A partir de 29 de junho, os ingressos ficarão mais caros.
Perda de quadros
O encadeamento principal executa o código responsável pelos eventos do tipo touch e pelo trabalho com a interface do usuário. Ele renderiza a tela. A maioria dos smartphones modernos processa a 60 quadros por segundo. Isso significa que as tarefas devem ser concluídas em 16,67 milissegundos (1000 milissegundos / 60 quadros). Portanto, a aceleração no segmento principal é importante.
Se alguma operação demorar mais de 16,67 milissegundos, a perda de quadros ocorrerá automaticamente e os usuários do aplicativo perceberão isso ao reproduzir animações. Em alguns dispositivos, a renderização é ainda mais rápida, por exemplo, no iPad Pro 2017, a taxa de atualização da tela é de 120 Hz, portanto, existem apenas 8 milissegundos para concluir as operações em um quadro.
Regra nº 1
CADisplayLink
é um cronômetro especial iniciado durante a sincronização vertical (Vsync). A sincronização vertical garante que não sejam alocados mais de 16,67 milissegundos para renderizar um quadro. Como uma verificação no AppDelegate, você pode registrar o CADisplayLink
no loop de execução principal e, em seguida, terá uma função adicional que executará os cálculos. Você pode acompanhar a duração do aplicativo e descobrir quanto tempo se passou desde o último lançamento desta função.
.
A inicialização ocorre quando uma necessidade de renderização aparece. Se muitas operações diferentes foram executadas e sobrecarregaram o encadeamento principal, essa função inicia com um atraso de 100 milissegundos. Isso significa que muito trabalho foi feito e, naquele momento, houve perda de pessoal.
Aqui está o aplicativo Catstagram. Ao baixar imagens, o aplicativo começa a ficar mais lento. Vemos que a taxa de quadros diminuiu em um determinado ponto e o tempo de carregamento durou cerca de 200 milissegundos. Algo parece estar demorando muito.
.
Os usuários não ficarão encantados com isso, principalmente se o aplicativo for executado em dispositivos mais antigos, como iPhone 5 ou iPod, etc.
Profiler de tempo
Uma ferramenta útil para rastrear esses problemas é o Time Profiler. Outras ferramentas também são úteis, mas no final da Fyusion 90% do tempo usamos o Time Profiler. Normalmente, os problemas em um aplicativo estão relacionados ao ScrollView, áreas com texto e imagens.
As imagens são importantes. Decodificamos o formato JPEG usando UIImage
. Eles fazem isso devagar e não podemos acompanhar diretamente o desempenho deles. Isso não acontece imediatamente após a configuração da imagem no UIImageView
, mas você pode ver esse momento através do rastreamento no Time Profiler.
A formatação de texto é outro ponto importante. É importante quando o aplicativo possui uma grande quantidade de texto "complexo", por exemplo, em japonês ou chinês. Pode levar muito tempo para calcular os tamanhos corretos para as linhas com texto.
A marcação da interface também diminui a renderização no aplicativo. Isto é especialmente verdade para a ferramenta AutoLayout. O AutoLayout é conveniente de usar, mas reduz a velocidade do aplicativo em comparação com a marcação manual. Temos que fazer concessões. Se o AutoLayout diminuir a velocidade do aplicativo, talvez seja hora de abandoná-lo e tentar outros tipos de marcação.
Padrão de rastreamento

Neste exemplo de árvore de chamada, você pode ver que tipo de trabalho a CPU faz. Você pode alterar o tipo de rastreio, observá-lo do ponto de vista de encadeamentos, processadores. Geralmente, o mais interessante é dividir o rastreamento em threads e monitorar o thread principal.
A análise de rastreamento inicial pode parecer complicada. Nem sempre é possível descobrir imediatamente o que significa FRunLoopDoSource0
.
Revendo o rastreamento, você pode entender como o sistema funciona e, em seguida, tudo faz sentido. Você pode rastrear o rastreamento da pilha e examinar todos os elementos do sistema que você não gravou. Mas na parte inferior está o seu código fonte.
Árvore de chamadas
Suponha que tenhamos uma aplicação muito simples. Ele contém a função principal que chama várias outras funções. A essência do trabalho do Time Profiler é que ele captura instantâneos do estado atual do rastreamento da pilha com uma frequência de um milissegundo (por padrão). Depois de mais um milissegundo, ele tira um instantâneo do rastreamento. Ele chama a função principal, que chama a função " foo
", que chama a função " bar
". O rastreamento de pilha inicial é mostrado na captura de tela abaixo. Esses dados são coletados juntos. Em frente a cada função, um número é indicado: 1, 1, 1.

Isso significa que cada uma dessas funções foi chamada uma vez. Depois de um milésimo de segundo, temos outra foto da pilha. Desta vez, parece exatamente o mesmo, então todos os números aumentam em 1 e obtemos 2, 2, 2.

Durante o terceiro milissegundo, nossa pilha de chamadas parece um pouco diferente. A função principal chama a bar
diretamente. Portanto, mais uma unidade é adicionada à função principal e à função “ bar
” e seu valor se torna 3. Em seguida, ocorre a separação. Às vezes, a função principal chama " foo
" diretamente, às vezes " bar
" é chamada diretamente. Isso aconteceu uma vez. Uma função foi chamada através de outra.
Em seguida, uma função chamada outra, que chamava de terceira função. Vemos que a função " baz
" foi chamada duas vezes. Mas essa função é tão insignificante que é chamada mais rapidamente que um milissegundo.
Ao usar o Time Profiler, é importante lembrar que ele não mostra intervalos de tempo específicos. Não exibe o tempo exato de execução de uma função. Ele apenas relata a frequência com que aparece nas figuras, o que fornece apenas um valor aproximado da duração de cada função. Como alguns processos são rápidos o suficiente, eles nunca são exibidos nas imagens.

Ao alternar as chamadas para o modo console, você pode ver e comparar todos os momentos de uma diminuição na taxa de quadros. No exemplo, a perda de quadros ocorreu várias vezes e vários processos foram realizados.

Clicar com a tecla Alt-Alt pressionada no macOS expandirá a seção e as subseções, não apenas a selecionada. Eles serão classificados pela quantidade de trabalho realizado. Em 90% dos casos, o CFRunLoopRun
é o CFRunLoopRun
, seguido pelos retornos de chamada.
Este aplicativo é baseado inteiramente em um único ciclo de execução de tarefas Executar Loop. Há um ciclo de repetição infinita e, a cada iteração, os retornos de chamada são iniciados. Se você observar esses retornos de chamada, poderá destacar os principais gargalos.
Tendo analisado esses desafios com mais detalhes, você provavelmente não entenderá o que eles estão fazendo. Estes podem ser processados, provedor de imagens, IO.

Existe uma opção que permite ocultar as bibliotecas do sistema. Na verdade, elas são as áreas problemáticas do aplicativo.
Existem medidores que, em termos percentuais, mostram quanto trabalho uma determinada função ou operação realiza. Se olharmos para este exemplo, veremos aqui o valor - 34%. Este é o processo jpeg_decode_image_all
da Apple. Depois de estudar, fica claro que a decodificação das imagens JPEG ocorre no encadeamento principal e, na maioria dos casos, essa é a causa da perda de quadros.

Regra nº 2
Em geral, a decodificação de imagens JPEG deve ser feita em segundo plano. A maioria das bibliotecas de terceiros (AsyncDisplayKit, SDWebImage etc.) pode fazer isso por padrão. Se você não quiser usar estruturas, poderá decodificar manualmente. Para fazer isso, você pode escrever uma extensão sobre UIImage
na qual cria um contexto e desenha manualmente uma imagem.

Ao executar esta operação, você pode chamar a função decodeImage
não é do thread principal. Ele sempre retornará uma imagem decodificada. Não há como verificar se uma imagem UIImage específica passou pela decodificação; portanto, você sempre precisa passá-las por esse método. Mas se você armazenar em cache os dados corretamente, não haverá processos desnecessários no sistema.
Do ponto de vista técnico, isso é menos eficaz. O uso da classe UIImageView
parece otimizado e eficiente. Mas ele também realiza decodificação de hardware, por isso também tem suas desvantagens. Com esse método, suas imagens serão decodificadas mais lentamente. Mas há boas notícias - você pode decodificar a imagem da maneira acima, não no segmento principal, e depois retornar ao segmento principal e configurar a interface.

Apesar de essa operação exigir mais tempo, ela pode não ser executada no encadeamento principal, o que significa que não interfere na atividade do usuário no aplicativo, pois não diminui a rolagem da fita. Solução rentável.
Alertas de falta de memória
Com qualquer sinal de pouca memória, desejo excluir todos os dados não utilizados possíveis. Porém, se vários processos forem executados em fluxos de terceiros, a colocação de imagens JPEG decodificadas volumétricas sobre eles ocupará a maior parte do espaço livre.
Esse problema ocorreu no aplicativo Fyuse. Se eu tivesse decodificado todas as minhas imagens JPEG em um fluxo de terceiros, em alguns casos, por exemplo, em modelos de telefone mais antigos, esse sistema interromperia instantaneamente o aplicativo. Isso se deve ao fato de que os fluxos de tarefas de terceiros não respondem a um aviso sobre memória insuficiente do sistema, como "Ei, exclua dados desnecessários!". Ocorre a seguinte situação: primeiro, você coloca todas essas imagens em fluxos de terceiros e, em seguida, o aplicativo trava constantemente. Se encadeamentos de terceiros enviarem sinais ao encadeamento principal sobre o que está acontecendo no sistema, esse problema não ocorrerá.
Trabalhe sem falhas

Essencialmente, o encadeamento principal é uma fila que consiste em processos. Ao trabalhar com encadeamentos de terceiros, você pode gravar o comando performSelectorOnMainThread:withObject:waitUntilDone:
em Objective-C. Graças a ela, as tarefas serão colocadas no final da fila no thread principal. Portanto, se o encadeamento principal estiver ocupado processando notificações de falta de memória, chamar este comando permitirá que você espere até que todas as notificações tenham sido processadas e só então inicie o processo complexo de carregar e colocar dados. No Swift, isso parece um pouco mais simples. DispatchQueue.main.sync
libera espaço no thread principal.
Aqui está outro exemplo. Liberamos memória e decodificamos imagens em fluxos de terceiros. A rolagem visual da fita ficou muito melhor. Ainda estamos perdendo quadros devido ao fato de estarmos testando o iPod 5g. Este é um dos piores modelos de teste daqueles que ainda suportam as versões 10 e 11 do iOS.

Se você tiver esse tipo de perda de quadro, ainda poderá ver a fita. No entanto, ainda existem processos que continuam a gerar perda de pessoal. Existem outras maneiras de tornar o aplicativo mais rápido.
Obviamente, nem sempre é fácil otimizar o aplicativo. Mas se você tiver tarefas que levam um tempo relativamente longo para concluir, coloque-as nos threads de segundo plano. Verifique se essas tarefas não estão relacionadas à interface do usuário, pois muitas classes do UIKit não são seguras para threads, ou seja, você não pode criá-las no back-end.
Use Core Graphics se você precisar processar imagens em um fluxo de terceiros. Não oculte a exibição das bibliotecas do sistema. Lembre-se dos avisos de falta de memória.
Bem-vindo ao MBLT DEV 2018
Entre 28 de setembro e a 5ª Conferência Internacional de Desenvolvedores Móveis MBLT DEV 2018 em Moscou. Os primeiros palestrantes já estão no site e o último madrugador ainda está à venda. Os preços dos ingressos subirão em 29 de junho. Compre ingressos agora pelo menor preço.

Leia sobre a implementação da interface do usuário no iOS, o uso das curvas de Bezier e outras ferramentas úteis na segunda parte do artigo, que publicaremos em 28 de junho.