Em um
artigo anterior, aprendemos sobre os motivos da instabilidade dos testes de unidade e como lidar com isso. Agora, queremos considerar uma das novas ferramentas da Apple para depuração e criação de perfil de código. Estamos falando da estrutura de registro os_log apresentada na WWDC 2018, que foi expandida pela ferramenta de análise de desempenho os_signpost.

Em um dos sprints, fomos incumbidos de implementar a geração de um documento pdf no lado do cliente. Concluímos a tarefa e demonstramos com sucesso os resultados para a equipe. Mas queríamos garantir a eficácia das nuances técnicas da decisão. Letreiro nos ajudou com isso. Com isso, conseguimos acelerar a exibição do documento várias vezes.
Para saber mais sobre a tecnologia de aplicativos os_signpost, veja onde ela pode ajudá-lo e como ela já nos ajudou, entre no assunto.
Aprofundar a questão
Existem muitos aplicativos no telefone do usuário, e todos eles usam recursos comuns do sistema: CPU, RAM, rede, bateria, etc. Se o seu aplicativo executar suas tarefas e não falhar, isso não significa que ele funcione de maneira eficiente e correta. Abaixo, descrevemos os casos que você pode encontrar potencialmente.
Um algoritmo abaixo do ideal pode levar a uma longa carga da CPU.- No início do aplicativo, após 20 segundos de espera, o sistema desligará o aplicativo e o usuário nem verá a primeira tela. Nesse caso, o sistema definirá um relatório de falha, cujo recurso distintivo será o tipo de exceção - EXC_CRASH (SIGKILL) com o tipo 0x8badf00d .
- Processos que consomem muitos recursos no encadeamento em segundo plano podem afetar a capacidade de resposta da interface do usuário, aumentar o consumo da bateria e forçar o aplicativo a encerrar o sistema (no caso de superaquecimento prolongado da CPU).
Casos de RAM:As especificações para os telefones no site da Apple não fornecem informações sobre RAM, mas outras fontes fornecem a seguinte alocação de memória para os modelos de telefone:
Tipo
| 4S
| 5
| 5C
| 5s
| 6
| 6P
| 6S
| 6SP
|
RAM, GB
| 0,5
| 1
| 1
| 1
| 1
| 1
| 2
| 2
|
Tipo
| SE
| X
| 7
| 7P
| 8
| 8P
| XS
| XSM
| Xr
|
RAM, GB
| 2
| 3
| 2
| 3
| 2
| 3
| 4
| 4
| 3
|
Quando há muito pouca RAM livre, o iOS começa a procurar memória para liberar, enviando simultaneamente um aviso de memória para todos os aplicativos em execução. Esse processo afeta implicitamente a CPU e a bateria do dispositivo. Se o aviso de memória for ignorado e a alocação de memória continuar, o sistema forçará o processo do aplicativo. Para o usuário, isso parece travamento, sem retrocessos no relatório de travamento.
Uso excessivo de solicitações de rede . Isso também leva a uma diminuição na duração da bateria. A duplicação de solicitações e / ou a falta de cancelamento de solicitações desnecessárias levam ao uso ineficiente da CPU.
Não se esqueça do CoreLocation . Quanto mais solicitada e com mais frequência e precisão a localização do usuário, mais a bateria do dispositivo é gasta. Para verificar a correção do processamento dos casos descritos, sugerimos o uso de os_signpost para criar um perfil dos processos do aplicativo e, em seguida, analisar os dados obtidos.
Integração de ferramentas no projeto
No nível superior, o processo de criação de um PDF consiste em três etapas:
- receber dados pela rede;
- formação de documentos;
- exibição na tela - decidimos dividir e registrar os estágios da geração do documento, começando com o usuário clicando no botão "Gerar" e terminando com a exibição do documento na tela.
Suponha que tenhamos a tarefa de analisar uma solicitação de rede assíncrona. A marcação no código ficará assim:
import os.signpost let pointsOfInterestLog = OSLog(subsystem: "com.example.your-app", category: . pointsOfInterest) let networkLog = OSLog(subsystem: "com.example.your-app", category: "NetworkOperations") os_signpost(.event, log: pointsOfInterestLog, name: "Start work") os_signpost(.begin, log: networkLog, name: "Overall work") for element in elements { os_signpost(.begin, log: networkLog, name: "Element work") makeWork(for: element) os_signpost(.end, log: networkLog, name: "Element work") } os_signpost(.end, log: networkLog, name: "Overall work")
As etapas para usar a orientação são as seguintes:
- Importe a estrutura os.signpost.
- Crie uma instância do OSLog. Vale a pena considerar que existem vários tipos de eventos: para eventos de intervalo (por exemplo, uma solicitação de rede), você pode usar uma categoria arbitrária e para eventos simultâneos (por exemplo, clicar em um botão), a categoria predefinida pointsOfInterest / OS_LOG_CATEGORY_POINTS_OF_INTEREST.
- Para eventos de intervalo, chame a função os_signpost com os tipos .begin e .end no início e no final do estágio sob investigação. Para eventos simultâneos, use o tipo .event.
- Se o código sob investigação puder ser executado de forma assíncrona, adicione um ID de Orientação, que permitirá separar os intervalos do mesmo tipo de operações com objetos diferentes.
- Opcionalmente, você pode adicionar dados adicionais (metadados) aos eventos despachados. Por exemplo, o tamanho das imagens baixadas pela rede ou o número da página PDF gerada. Essas informações ajudarão a entender o que exatamente acontece no estágio investigado da execução do código.
Da mesma forma no obj-c:
@import os.signpost; os_log_t pointsOfInterestLog = os_log_create("com.example.your-app", OS_LOG_CATEGORY_POINTS_OF_INTEREST); os_log_t networkLog = os_log_create("com.example.your-app", "NetworkOperations"); os_signpost_id_t operationIdentifier = os_signpost_id_generate(networkLog); os_signpost_event_emit(pointsOfInterestLog, operationIdentifier, "Start work"); os_signpost_interval_begin(networkLog, operationIdentifier, "Overall work"); for element in elements { os_signpost_id_t elementIdentifier = os_signpost_id_make_with_pointer(networkLog, element); os_signpost_interval_begin(networkLog, elementIdentifier, "Element work"); [element makeWork]; os_signpost_interval_end(networkLog, elementIdentifier, "Element work"); } os_signpost_interval_end(networkLog, operationIdentifier, "Overall work");
Para uma nota. Se o projeto deve ser executado no iOS antes da versão 12.0, o Xcode oferecerá para agrupar as chamadas os_signpost na construção if #available. Para não bagunçar o código, você pode colocar essa lógica em uma classe separada.
Vale a pena considerar que os_signpost requer uma string estática literal como parâmetro do nome do evento. Para adicionar uma digitação mais rigorosa, você pode criar uma enumeração com tipos de eventos e, na implementação da classe, mapeá-los para literais de string. Colocar o OSLog em uma classe separada adicionará a lógica para desativá-lo no esquema de liberação (existe um comando OSLog separado para isso).
import os.signpost let networkLog: OSLog if ProcessInfo.processInfo.environment.keys.contains("SIGNPOSTS_FOR_NETWORK") { networkLog = OSLog(subsystem: "com.example.your-app", category: "NetworkOperations" } else { networkLog = .disabled }
Você pode adicionar valores de qualquer propriedade à marcação do evento com os seguintes decodificadores de tipo para formatação conveniente:
Tipo de valor
| Especificador personalizado
| Saída de exemplo
|
time_t
| % {time_t} d
| 2016-01-12 19:41:37
|
timeval
| % {timeval}. * P
| 2016-01-12 19: 41: 37.774236
|
timespec
| % {timespec}. * P
| 2016-01-12 19: 41: 37.2382382823
|
errno
| % {errno} d
| Tubo quebrado
|
iec-bytes
| % {iec-bytes} d
| 2,64 MiB
|
taxa de bits
| % {bitrate} d
| 123 kbps
|
IEC-bitrate
| % {iec-bitrate} d
| 118 kibps
|
uuid_t
| % {uuid_t}. * 16P % {uuid_t}. * P
| 10742E39-0657-41F8-AB99-878C5EC2DCAA
|
Agora, ao criar um perfil do aplicativo, os eventos do os_signpost serão enviados para o Instruments na forma de dados tabulares. Para alternar para ferramentas, use o atalho de teclado Cmd + I e selecione a ferramenta necessária para criação de perfil. Para ver os dados marcados, basta ativar as ferramentas os_signpost e Point of Interest no lado direito da interface da ferramenta.

Por padrão, os eventos são agrupados em categorias e exibidos em uma tabela, onde são calculados o número e as estatísticas no tempo de execução. Além disso, há uma exibição gráfica na linha do tempo, o que facilita a comparação dos eventos recebidos com os resultados em outras ferramentas. Também há a possibilidade de personalizar a exibição de estatísticas e escrever sistemas especialistas - mas este tópico merece um artigo separado.
Exemplos de uso
Caso nº 1. PDFKit vs WKWebView
Com o uso do os_signpost, vimos que, para documentos pequenos (algumas páginas), o passo mais longo era o último passo - exibir o documento - em vez de trabalhar com uma rede ou gráficos. Isso nos levou à decisão de substituir o
WKWebView pelo
PDFView , o que acelerou a exibição do documento de 1,5 segundos para 30 milissegundos. Nos gráficos, fica assim:
Exibir um documento PDF (WKWebView) no Time Profiler
Exibir um documento PDF (PDFView) no Time ProfilerOs dados resultantes podem ser implementados em outras ferramentas fornecidas pelo Xcode. Como a ferramenta Alocações mostrou, o ganho na velocidade de download foi alcançado aumentando o uso da RAM.
Caso nº 2. Aviso de baixa memória
Um documento PDF é gerado de forma assíncrona e sua formação requer a alocação de uma quantidade significativa de memória. No caso de memória insuficiente, decidimos adicionar a capacidade de interromper a operação assíncrona da criação de um documento.
Como você sabe, ao usar o NSOperationQueue, o método cancelAllOperation libera uma fila existente, mas não para de já executar as operações. A partir disso, concluímos que, na implementação da operação, é necessário determinar periodicamente sua condição e parar de funcionar. Assim, liberando recursos se estiver definido como status Cancelado.
O próximo passo é uma operação assíncrona que precisamos verificar para cancelamento. Mas, ao mesmo tempo, não está claro com que frequência fazer essa verificação. Tínhamos duas opções - verificação linha por linha e página por página. os_signpost também ajudou aqui. Como se viu, adicionando uma verificação de cancelamento no ciclo linha a linha de renderização da tabela no documento, aumentamos o tempo necessário para gerar o documento (em 150 páginas) em 2 vezes. A segunda opção foi mais ideal em termos de desempenho e, na verdade, não aumentou o tempo necessário para criar o documento. Como resultado, quando recebemos o evento de aviso de memória, cancelamos a operação programaticamente e exibimos a tela de erro para o usuário.
Para garantir que a memória seja realmente liberada, também podemos usar os_signpost. Desta vez, adicionando um marcador sobre o início do evento no método didRecieveMemoryWarning e um marcador sobre o final no viewDidLoad da tela de erro. A propósito, você pode emular um evento de memória insuficiente no simulador (shift + command + m).
Caso nº 3. Atualizar restrições
Placa de sinalização pode ser útil no processo de layout. Para criar restrições, usamos a estrutura de
alvenaria . A documentação da estrutura diz que é recomendável usar o método updateConstraints () para criar construções. Mas a Apple não recomenda isso, e você pode verificar isso com a marcação da placa de sinalização.

De acordo com a documentação da Apple, updateConstraints só deve ser usado para alterar restrições se não pudermos fazê-lo no local em que a alteração ocorreu.

Após analisar os resultados, concluímos que em nosso aplicativo a chamada updateConstraints não é tão frequente - aproximadamente toda vez que a exibição aparece na tela.
Apesar disso, para evitar possíveis defeitos de desempenho, recomendamos seguir os conselhos da Apple sobre isso.
Conclusões
Em 2018, a Apple forneceu aos desenvolvedores a oportunidade de expandir independentemente as ferramentas de criação de perfil. Obviamente, você pode usar outras ferramentas de depuração: pontos de interrupção, saída para o console, timers, perfis personalizados. Mas eles levam mais tempo para implementar ou nem sempre dão uma imagem completa do que está acontecendo.
No próximo artigo, consideraremos como usar as informações recebidas da placa de sinalização com mais eficiência, escrevendo nosso próprio sistema especialista (Instrumentos Personalizados).
Links úteisO artigo foi escrito com @victoriaqb - Victoria Kashlina, desenvolvedora do iOS.