← Todos os Posts

MetricKit reconstruído: telemetria ciente de estado no iOS 27

O app de demonstração da Apple na WWDC 2026 reportava uma taxa de travamento de rolagem de 15 milissegundos por segundo, calculada como média ao longo de um dia inteiro de uso. Separada por aba, a mesma informação contava uma história completamente diferente: 1 ms/s em uma aba, 71 ms/s na outra.1 Uma tela estava quase impecável; a outra, nas palavras da sessão, “passava por interrupções críticas”.1 O número combinado escondia os dois fatos. A sessão 222, “Meet the new MetricKit”, é a história de como o iOS 27 fecha essa lacuna: uma reconstrução completa da superfície de API do framework e um novo framework complementar, StateReporting, que transforma métricas de campo de todo o app em métricas por estado. A telemetria de campo finalmente consegue responder à pergunta que todo engenheiro de performance faz primeiro: qual tela está lenta?

TL;DR

  • No iOS 27, o MetricKit “foi reconstruído do zero com uma API moderna, contextualmente rica, expressiva e Swift-first”, e cada nova capacidade apresentada na sessão é exclusiva das novas APIs.1
  • O ponto de entrada é a classe MetricManager. Os apps aguardam os streams assíncronos metricReports e diagnosticReports no lançamento, e os dois tipos de relatório são Codable, de modo que um JSONEncoder os envia direto para o seu servidor de analytics.1
  • Os relatórios são estruturados: intervalEntries contém uma entrada de dia inteiro mais detalhamentos menores, organizados em grupos de métricas como .cpu, .memory, .display e .gpu, até valores individuais como peakMemory.1
  • Dados novos no iOS 27: uma métrica de taxa de quadros do Metal para a performance de renderização, diagnósticos de exceção de memória para encerramentos por limite de memória e uma category de crash que liga cada diagnóstico de crash às suas tendências de métricas.1
  • O recurso de destaque é o framework StateReporting: reporte o estado em que o seu app está (aba ativa, braço do experimento, configuração de tela) e o MetricKit agrega as métricas por estado, substituindo um único número combinado por um detalhamento por tela.1

Reconstruído do zero

Assista: Meet the new MetricKit (WWDC26)

Yonni, engenheira da equipe do MetricKit, apresenta a reconstrução do iOS 27 a partir de 1:23.

O trabalho do MetricKit não mudou: ele é “a parte de coleta” do fluxo de performance, fornecendo dois tipos de dados. As métricas dizem se uma área de performance está melhorando ou piorando no geral; os diagnósticos dizem qual caminho de código causou um problema.1 O que mudou foi tudo a respeito de como você recebe esses dados. A sessão coloca de forma direta: no iOS 27 o framework “foi reconstruído do zero com uma API moderna, contextualmente rica, expressiva e Swift-first”, e “todos os avanços que vou discutir hoje são exclusivos desse novo conjunto de APIs”.1

O novo ponto de entrada é a classe MetricManager. Em vez de registrar um delegate e fazer o parsing de payloads, você aguarda os relatórios pela propriedade metricReports na forma de um stream assíncrono. Duas regras operacionais vêm diretamente da sessão: faça a configuração na inicialização do app “para evitar qualquer perda de dados por uma assinatura tardia” e mantenha o MetricManager vivo “para que os streams possam continuar entregando relatórios à medida que dados subsequentes ficam prontos”.1 A Apple recomenda rodar esse trabalho em uma detached task ou em uma classe de serviço dedicada assim que o app for lançado.1

A sessão apresenta o código em slides, então os trechos abaixo são formas ilustrativas de chamada que correspondem à descrição; confirme as assinaturas exatas na documentação da Apple antes de publicar.

// Illustrative call shape based on session 222; verify against the docs.
let manager = MetricManager()

Task.detached {
    for await report in manager.metricReports {
        // Encode and ship, or inspect specific groups.
    }
}

Enviar um relatório para o seu servidor costumava significar lidar com dados de payload opacos. Agora os valores MetricReport são Codable: “Basta criar um JSONEncoder e codificar o relatório inteiro.”1 Se você quiser um valor específico em vez do documento inteiro, o relatório é totalmente estruturado. Você itera pelos intervalEntries, que “incluem uma entrada agregada de dia inteiro e janelas de detalhamento menores quando disponíveis”, normalmente de algumas horas cada e presentes apenas quando existem métricas para elas.1 Dentro de cada intervalo, as métricas são organizadas em grupos de métricas, em que “cada grupo representa um aspecto do sistema, coisas como .cpu, .memory, .display e .gpu”.1 Filtre até o grupo que lhe interessa (o exemplo da sessão extrai memoryMetrics) e então faça o switch sobre os casos de métrica para chegar a um valor individual como peakMemory.1

O catálogo de métricas também cresce no iOS 27. Ao lado dos histogramas de tempo de lançamento (o exemplo da sessão mostra a maioria dos lançamentos entre 510 e 540 milissegundos), travamentos, métricas de animação e consumo de recursos como CPU, GPU, gravações em disco e transferências de rede, o MetricKit adiciona uma métrica de taxa de quadros do Metal. A sessão chama a taxa de quadros de “uma métrica fundamental para que os desenvolvedores de jogos entendam a performance de renderização” e aponta para “Find and fix performance issues in your Metal game” no lado da otimização.1

Diagnósticos: backtraces, exceções de memória e categorias de crash

As métricas dizem que algo regrediu; os diagnósticos dizem onde. Quando algo dá errado, como um crash ou um travamento, “o sistema captura um diagnóstico no dispositivo”, e um relatório de diagnóstico “empacota os detalhes e os entrega imediatamente ao seu app por meio do MetricKit”.1 Muitos diagnósticos incluem backtraces que mostram a pilha de chamadas exata no momento do evento. Na demonstração da sessão, o backtrace simbolizado começa no início da thread, em código do sistema, atravessa para dentro do app e para na função submitReport() do app, que marca o ponto de falha e o lugar para mirar a correção.1

Os diagnósticos de crash carregam um backtrace, o motivo do encerramento e um tipo de exceção. Novidade no iOS 27, uma category de encerramento “indica como cada crash foi contabilizado nas métricas”, de modo que “se os encerramentos anormais estiverem em tendência de alta, você pode correlacioná-los diretamente com diagnósticos individuais”.1 A linha de métrica no seu painel e os relatórios de crash individuais por trás dela finalmente compartilham uma chave.

O iOS 27 também adiciona diagnósticos de exceção de memória: “quando o seu app ou extensão é encerrado por exceder o limite de memória, você ganha mais visibilidade sobre o que aconteceu”.1 As extensões estão explicitamente no escopo, o que importa para quem precisa depurar à distância encerramentos de memória de widgets ou extensões.

O consumo espelha o lado das métricas. Você aguarda diagnosticReports na sua instância de MetricManager, novamente desde o lançamento do app em uma detached task ou classe de serviço, e os valores DiagnosticReport são Codable para o mesmo pipeline de codificar e enviar.1 Como os relatórios são estruturados, você pode fazer o switch sobre os casos de diagnóstico: o caso de crash entrega o backtrace, o motivo e a category, enquanto um caso de travamento pode ser roteado para um processamento diferente.1

// Illustrative call shape based on session 222; verify against the docs.
for await report in manager.diagnosticReports {
    switch /* diagnostic case */ {
    case /* crash */: break  // backtrace, reason, category
    case /* hang */:  break  // handle separately
    default:          break
    }
}

StateReporting: de um único número combinado à verdade por tela

Tudo acima ainda descreve a telemetria de todo o app, e a telemetria de todo o app tem um teto. O app de relatório de despesas da sessão torna o problema concreto. O app organiza seus recursos em uma aba Reports e uma aba Spending. Ao longo de um dia, o MetricKit reporta 4,5 segundos de tempo total de travamento em 5 minutos de rolagem: uma taxa de travamento de 15 ms/s. Mas esse número é “uma taxa média de travamento de rolagem ao longo de todo o uso do app, mesmo que alguém fique indo e voltando entre a aba Reports e a aba Spending”.1 Você sabe que o app trava. Você não sabe onde.

O novo framework StateReporting remove a mistura. Estados são “informações que você define e que descrevem a configuração ou o comportamento do seu app, para que o MetricKit possa agregar métricas em função dessas características”.1 À medida que as pessoas se movem entre as abas, o app reporta cada transição, e o MetricKit cruza esses estados com os dados de métricas e diagnósticos.1

O ganho na demonstração é o momento que justifica a reconstrução. Em vez de um único valor combinado de 15 ms/s, as métricas chegam por estado: a aba Spending rolava “incrivelmente suave”, a 1 ms/s, enquanto a aba Reports “disparava para 71 ms/s”.1 A sessão chega à conclusão que o número combinado jamais poderia sustentar: “a aba Spending está com ótimo desempenho! Mas a aba Reports está passando por interrupções críticas, e é exatamente aí que o seu esforço de otimização deve se concentrar”.1 Um número virou um veredito e uma ordem de serviço.

Os estados seguem um modelo de transição, não de delimitação por pares. “Não há pares de início ou fim — o app reporta a condição em que está, a qualquer momento”, e o MetricKit acompanha quanto tempo o app permanece em cada estado.1

Domínios, metadados e codificação por estado

Cada estado é vinculado a um domínio, que “descreve uma função ou área de um app”. Um domínio pode manter apenas um estado ativo por vez, e domínios separados permitem que vários estados estejam em andamento simultaneamente.1 O exemplo da sessão é um experimento A/B: com a mudança experimental ligada, as despesas são buscadas no banco de dados em lotes pequenos; desligada, em lotes maiores. Colocar o estado da aba e o estado do tamanho do lote em domínios separados significa que “o MetricKit vai entregar métricas separadas para cada aba e cada tamanho de lote”.1 Telemetria por tela e leituras de experimento a partir do mesmo pipeline, em campo.

A adoção tem três passos na sessão: importar o framework StateReporting, criar um domínio (“normalmente uma string de DNS reverso”) e registrá-lo quando você configura a sua instância de MetricManager, e então reportar as transições conforme o app entra em cada estado, como a transição para um estado identificado pela string “Reports”.1 Para uma granularidade mais fina, você define a sua própria struct com o macro ReportableMetadata, cria um StateReporter com esse tipo de metadados e reporta as transições com o rótulo e o seu tipo personalizado. O exemplo ViewConfiguration da sessão carrega um valor listSize e se a lista está ordenada.1 De novo: a sessão mostra esse fluxo em slides, sem assinaturas completas, então trate a forma como algo a confirmar na documentação, não como sintaxe a copiar.

No lado de recebimento, o relatório ganha um segundo eixo. Antes de qualquer estado ser reportado, a propriedade stateEntries do seu relatório de métricas fica vazia. Após a adoção, o relatório carrega valores StateEntry, cada um contendo “valores de métricas agregados ao longo do tempo passado naquele estado individual”.1 Para o pipeline do servidor, você pode agrupar a saída codificada por domínio: defina a chave encodingFormatKey na propriedade userInfo do seu JSONEncoder como byStateReportingDomain, e o relatório codificado apresenta tanto as state entries quanto as interval entries “agrupadas por cada domínio e estado que existe no relatório”.1

Boas práticas, e por onde começar

A sessão encerra com orientações que soam como conselhos de design de esquema conquistados a duras penas. Os domínios devem ser estreitamente delimitados a uma única área do app. As transições de estado “devem representar fases estáveis e significativas, não eventos transitórios de UI”.1 Projete cada estado de modo que, quando uma regressão aparecer, o estado por si só lhe dê informação suficiente para mirar a correção. E resista ao impulso de instrumentar tudo: “Estados demais podem resultar em dados granulares demais e, na verdade, podem dificultar a interpretação do quadro geral”, e existem limites superiores para o número de estados, a fim de minimizar o overhead (a sessão não fornece um valor).1 Antes de publicar, valide que os estados reportados correspondem às suas expectativas com o instrumento Points of Interest.1

O lado da coleta é apenas metade do sistema. A sessão é direta ao dizer que “analisar métricas em todos os dispositivos é um problema de ciência de dados”: você sobe um servidor que ingere os relatórios, agrega ao longo das dimensões que lhe interessam, estabelece uma baseline e monitora movimentos em qualquer direção.1 Os relatórios Codable e a codificação byStateReportingDomain existem para alimentar exatamente esse pipeline.

Para quem já adotou, a instrução final é explícita: “se você está usando a API MXMetricManager, migre para a nova API MetricManager para aproveitar todas essas novas capacidades”.1 As novas APIs são onde vive cada avanço da sessão, e a sessão as apresenta como “o futuro do framework”.1

FAQ

O que de fato mudou no MetricKit no iOS 27?

O framework foi reconstruído com uma API moderna e Swift-first. O ponto de entrada é a nova classe MetricManager; os relatórios de métricas e diagnósticos chegam como streams assíncronos aguardáveis (metricReports, diagnosticReports); os relatórios são Codable para codificação JSON direta; e a estrutura é navegável em código via intervalEntries e grupos de métricas. O iOS 27 também adiciona uma métrica de taxa de quadros do Metal, diagnósticos de exceção de memória, uma category de crash que liga os diagnósticos de crash à contabilização de métricas, e o framework StateReporting para métricas por estado.1

Como o StateReporting decide quais métricas pertencem a qual estado?

O seu app reporta transições: o estado para o qual está se movendo, dentro de um domínio que você define. O MetricKit acompanha quanto tempo o app permanece em cada estado e agrega os valores de métricas ao longo do tempo passado ali. Não há pares de início/fim; o app simplesmente reporta a condição em que está a qualquer momento. Cada estado então ganha o seu próprio StateEntry no relatório de métricas.1

Posso acompanhar mais de uma dimensão de uma vez, como tela e braço do experimento?

Sim. Cada domínio pode manter um estado ativo por vez, mas domínios separados rodam simultaneamente. O app de despesas da sessão coloca a aba ativa em um domínio e um experimento de tamanho de lote do banco de dados em outro, e o MetricKit entrega métricas separadas para cada aba e cada tamanho de lote.1

Devo reportar todo evento de UI como um estado?

Não. A sessão recomenda estados que representem fases estáveis e significativas em vez de eventos transitórios de UI, domínios estreitamente delimitados a uma única área do app e moderação no geral: estados demais tornam os dados mais difíceis de interpretar, e o sistema impõe limites superiores ao número de estados para minimizar o overhead. Valide os seus estados com o instrumento Points of Interest antes de publicar.1

Eu preciso sair do MXMetricManager?

A orientação da sessão é migrar do MXMetricManager para a nova API MetricManager, porque toda nova capacidade abordada (streams assíncronos, relatórios Codable, métricas cientes de estado, os novos tipos de métrica e diagnóstico) é exclusiva do novo conjunto de APIs.1


Neste ano, o MetricKit é a metade de campo de uma história em duas partes: o Instruments mostra o travamento no laboratório, e o MetricKit ciente de estado lhe diz quais telas travam para usuários reais, algo coberto pelo lado do laboratório em Instruments 27 e a responsividade dos apps. O trabalho de renderização que de fato corrige uma aba a 71 ms/s está em Performance e interoperabilidade do SwiftUI no iOS 27. E a razão pela qual as médias combinadas enganam, para começo de conversa, é o tema de o ponto cego de performance. O hub completo da série é a Apple Ecosystem Series.

References


  1. Apple, WWDC 2026 session 222, Meet the new MetricKit. Source for the iOS 27 ground-up rebuild and Swift-first API framing, the MetricManager entry point and the metricReports / diagnosticReports async streams, Codable reports and JSONEncoder usage, intervalEntries and metric groups (.cpu, .memory, .display, .gpu, peakMemory), the Metal frame rate metric, memory exception diagnostics, the crash termination category, the submitReport() backtrace walkthrough, the StateReporting framework (domains, transition model, StateReporter, ReportableMetadata, stateEntries, byStateReportingDomain via encodingFormatKey), the expense-app demo numbers (15 ms/s blended; 1 ms/s Spending tab versus 71 ms/s Reports tab), the state best practices and Points of Interest validation, and the guidance to migrate from MXMetricManager to MetricManager

Artigos relacionados

O que há de novo no Instruments 27 para a responsividade de apps

O Instruments 27 adiciona Top Functions, Run Comparisons, um instrument Swift executors e um novo painel Inspector para …

12 min de leitura

Performance e interoperabilidade do SwiftUI no iOS 27

Como o SwiftUI no iOS 27 lida com rolagem em lazy stacks, efeitos de shader na GPU e interoperabilidade com AppKit/UIKit…

16 min de leitura

De 76 a 100: Alcançando uma Pontuação Perfeita no Lighthouse

Como um site pessoal de portfólio saiu de uma pontuação de 76 no Lighthouse mobile com 0,493 de CLS para um perfeito 100…

7 min de leitura