MetricKit reconstruido: telemetría consciente del estado en iOS 27
La app de demostración de la WWDC 2026 de Apple reportó una tasa de tirones de scroll de 15 milisegundos por segundo, promediada a lo largo de un día entero de uso. Separada por pestaña, esos mismos datos contaban una historia completamente distinta: 1 ms/s en una pestaña, 71 ms/s en la otra.1 Una pantalla era casi impecable; la otra estaba, en palabras de la sesión, “experimentando interrupciones críticas”.1 El número combinado ocultaba ambos hechos. La sesión 222, “Meet the new MetricKit”, es la historia de cómo iOS 27 cierra esa brecha: una reconstrucción desde cero de la superficie de API del framework, y un nuevo framework complementario, StateReporting, que convierte las métricas de campo de toda la app en métricas por estado. La telemetría de campo por fin puede responder la pregunta que todo ingeniero de rendimiento se hace primero: ¿qué pantalla está lenta?
TL;DR
- En iOS 27, MetricKit “se reconstruyó desde cero con una API moderna, contextualmente rica, expresiva y centrada en Swift”, y cada nueva capacidad de la sesión es exclusiva de las nuevas APIs.1
- El punto de entrada es la clase
MetricManager. Las apps esperan los streams asíncronosmetricReportsydiagnosticReportsal iniciarse, y ambos tipos de reporte sonCodable, de modo que unJSONEncoderlos envía directamente a tu servidor de analítica.1 - Los reportes están estructurados:
intervalEntriescontiene una entrada de día completo más desgloses más pequeños, organizados en grupos de métricas como.cpu,.memory,.displayy.gpu, hasta valores individuales comopeakMemory.1 - Nuevos datos en iOS 27: una métrica de frame rate de Metal para el rendimiento de renderizado, diagnósticos de excepciones de memoria para las terminaciones por límite de memoria, y una
categoryde crash que vincula los diagnósticos individuales de crashes con tus tendencias de métricas.1 - La función estrella es el framework
StateReporting: reportas el estado en el que se encuentra tu app (pestaña activa, rama de experimento, configuración de vista) y MetricKit agrega las métricas por estado, reemplazando un único número combinado por un desglose por pantalla.1
Reconstruido desde cero
Yonni, un ingeniero del equipo de MetricKit, presenta la reconstrucción de iOS 27 a partir del minuto 1:23.
El trabajo de MetricKit no ha cambiado: es “la pieza de recolección” del flujo de trabajo de rendimiento, y proporciona dos tipos de datos. Las métricas te dicen si un área del rendimiento está mejorando o empeorando en general; los diagnósticos te dicen qué ruta de código causó un problema.1 Lo que cambió es todo lo relativo a cómo recibes esos datos. La sesión lo expone con claridad: en iOS 27 el framework “se reconstruyó desde cero con una API moderna, contextualmente rica, expresiva y centrada en Swift”, y “todos los avances de los que hablaré hoy son exclusivos de este nuevo conjunto de APIs”.1
El nuevo punto de entrada es la clase MetricManager. En lugar de registrar un delegate y parsear payloads, esperas los reportes a través de la propiedad metricReports como un stream asíncrono. Dos reglas operativas vienen directo de la sesión: haz la configuración al iniciar la app “para evitar cualquier pérdida de datos por una suscripción tardía”, y mantén MetricManager con vida “para que los streams puedan seguir entregando reportes a medida que los datos posteriores estén listos”.1 Apple recomienda ejecutar el trabajo en una tarea desacoplada (detached task) o en una clase de servicio dedicada en cuanto se inicia la app.1
La sesión presenta el código en diapositivas, así que los fragmentos a continuación son formas de llamada ilustrativas que coinciden con su descripción; confirma las firmas exactas contra la documentación de Apple antes de llevarlo a producción.
// 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 un reporte a tu servidor antes implicaba manejar datos de payload opacos. Ahora los valores de MetricReport son Codable: “Solo crea un JSONEncoder y codifica el reporte completo”.1 Si en lugar de todo el documento quieres un valor específico, el reporte está completamente estructurado. Iteras a través de intervalEntries, que “incluye una entrada agregada de día completo y ventanas de desglose más pequeñas cuando están disponibles”, típicamente de unas pocas horas cada una y presentes solo cuando existen métricas para ellas.1 Dentro de cada intervalo, las métricas se organizan en grupos de métricas, donde “cada grupo representa un aspecto del sistema, cosas como .cpu, .memory, .display y .gpu”.1 Filtra hasta el grupo que te interesa (el ejemplo de la sesión extrae memoryMetrics), y luego haz un switch sobre los casos de la métrica para llegar a un valor individual como peakMemory.1
El catálogo de métricas también crece en iOS 27. Junto con los histogramas de tiempo de lanzamiento (el ejemplo de la sesión muestra que la mayoría de los lanzamientos caen entre 510 y 540 milisegundos), los hangs, las métricas de animación y el consumo de recursos como CPU, GPU, escrituras a disco y transferencias de red, MetricKit añade una métrica de frame rate de Metal. La sesión llama al frame rate “una métrica clave para que los desarrolladores de juegos entiendan el rendimiento de renderizado” y remite a “Find and fix performance issues in your Metal game” para el lado de la optimización.1
Apóyate en la métrica de lanzamiento de MetricKit en lugar de instrumentar el lanzamiento por tu cuenta. Apple mide el tiempo de lanzamiento desde el momento en que el usuario toca el ícono de la app hasta que se dibuja el primer frame, lo cual ocurre antes de que tu proceso siquiera exista.3 Un temporizador hecho a mano solo puede empezar una vez que tu código se ejecuta, así que se pierde por completo la ventana previa a main y subestima el lanzamiento que tus usuarios realmente perciben. Lee el histograma que entrega MetricKit en vez de construir un temporizador que no puede ver la parte que importa.
Diagnósticos: backtraces, excepciones de memoria y categorías de crash
Las métricas te dicen que algo regresionó; los diagnósticos te dicen dónde. Cuando algo sale mal, como un crash o un hang, “el sistema captura un diagnóstico en el dispositivo” y un reporte de diagnóstico “empaqueta los detalles y los entrega de inmediato a tu app a través de MetricKit”.1 Muchos diagnósticos incluyen backtraces que muestran la pila de llamadas exacta en el momento del evento. En el recorrido de la sesión, el backtrace simbolizado comienza en el inicio del hilo dentro del código del sistema, cruza hacia la app y se detiene en la función submitReport() de la app, que marca el punto de fallo y el lugar donde dirigir la solución.1
Los diagnósticos de crash llevan un backtrace, el motivo de la terminación y un tipo de excepción. Nuevo en iOS 27, una category de terminación “indica cómo se contabilizó cada crash en las métricas”, de modo que “si las terminaciones anormales muestran una tendencia al alza, puedes correlacionarlas directamente con diagnósticos individuales”.1 La línea de métrica en tu dashboard y los reportes individuales de crash que la respaldan por fin comparten una clave.
iOS 27 también añade diagnósticos de excepciones de memoria: “cuando tu app o extensión se termina por exceder su límite de memoria, obtienes más información sobre lo que pasó”.1 Las extensiones están explícitamente dentro del alcance, lo cual importa para cualquiera que esté depurando cierres por memoria de un widget o una extensión a distancia.
El consumo refleja el lado de las métricas. Esperas diagnosticReports en tu instancia de MetricManager, de nuevo desde el lanzamiento de la app en una tarea desacoplada o clase de servicio, y los valores de DiagnosticReport son Codable para el mismo pipeline de codificar y enviar.1 Como los reportes están estructurados, puedes hacer un switch sobre los casos de diagnóstico: el caso de crash entrega el backtrace, el motivo y la category, mientras que un caso de hang puede dirigirse a un procesamiento 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 un número combinado a la verdad por pantalla
Todo lo anterior sigue describiendo telemetría de toda la app, y la telemetría de toda la app tiene un techo. La app de reportes de gastos de la sesión vuelve concreto el problema. La app organiza sus funciones en una pestaña Reports y una pestaña Spending. A lo largo de un día, MetricKit reporta 4,5 segundos de tiempo total de tirones en 5 minutos de scroll: una tasa de tirones de 15 ms/s. Pero ese número es “una tasa promedio de tirones de scroll sobre todo el uso de la app, incluso si alguien va y viene entre la pestaña Reports y la pestaña Spending”.1 Sabes que la app tiene tirones. No sabes dónde.
El nuevo framework StateReporting elimina la mezcla. Los estados son “información que tú defines y que describe la configuración o el comportamiento de tu app, para que MetricKit pueda agregar las métricas en función de esas características”.1 A medida que las personas se mueven entre pestañas, la app reporta cada transición, y MetricKit interseca esos estados con los datos de métricas y diagnósticos.1
La recompensa en la demo es el momento que justifica la reconstrucción. En lugar de una única cifra combinada de 15 ms/s, las métricas llegan por estado: la pestaña Spending hizo scroll “increíblemente suave” a 1 ms/s, mientras que la pestaña Reports “se disparó a 71 ms/s”.1 La sesión extrae la conclusión que el número combinado nunca podría sostener: “¡la pestaña Spending está rindiendo de maravilla! Pero la pestaña Reports está experimentando interrupciones críticas, y ahí es exactamente donde debería enfocarse tu esfuerzo de optimización”.1 Un número se convirtió en un veredicto y una orden de trabajo.
Los estados siguen un modelo de transición, no de delimitación por pares. “No hay pares de inicio o fin: la app reporta la condición en la que está, en cualquier momento dado”, y MetricKit rastrea cuánto tiempo permanece la app en cada estado.1
Dominios, metadatos y codificación por estado
Cada estado pertenece a un dominio, que “describe una función o área de una app”. Un dominio puede contener solo un estado activo a la vez, y dominios separados permiten que varios estados estén en curso simultáneamente.1 El ejemplo de la sesión es un experimento A/B: con un cambio experimental activado, los gastos se obtienen de la base de datos en lotes pequeños; desactivado, en lotes más grandes. Colocar el estado de la pestaña y el estado del tamaño de lote en dominios separados significa que “MetricKit entregará métricas separadas para cada pestaña y cada tamaño de lote”.1 Telemetría por pantalla y lecturas de experimentos desde el mismo pipeline, en el campo.
La adopción tiene tres pasos en la sesión: importar el framework StateReporting, crear un dominio (“típicamente una cadena de DNS inverso”) y registrarlo cuando configures tu instancia de MetricManager, y luego reportar las transiciones a medida que la app entra en cada estado, como hacer la transición a un estado identificado por la cadena “Reports”.1 Para un grano más fino, defines tu propia struct con la macro ReportableMetadata, creas un StateReporter con ese tipo de metadatos, y reportas las transiciones con la etiqueta y tu tipo personalizado. El ejemplo ViewConfiguration de la sesión lleva un valor listSize y si la lista está ordenada.1 De nuevo: la sesión muestra este flujo en diapositivas sin las firmas completas, así que trata la forma como algo a confirmar en la documentación, no como sintaxis para copiar.
En el lado receptor, el reporte gana un segundo eje. Antes de que se reporte cualquier estado, la propiedad stateEntries de tu reporte de métricas está vacía. Tras la adopción, el reporte lleva valores de StateEntry, cada uno conteniendo “valores de métrica agregados a lo largo del tiempo transcurrido en ese estado individual”.1 Para el pipeline del servidor, puedes agrupar la salida codificada por dominio: establece la clave encodingFormatKey en la propiedad userInfo de tu JSONEncoder con el valor byStateReportingDomain, y el reporte codificado presenta tanto las state entries como las interval entries “agrupadas por cada dominio y estado que existe en el reporte”.1
Buenas prácticas, y por dónde empezar
La sesión cierra con orientación que se lee como un consejo de diseño de esquemas ganado a pulso. Los dominios deben estar acotados estrechamente a un área de la app. Las transiciones de estado “deben representar fases estables y significativas, no eventos transitorios de la UI”.1 Diseña cada estado de modo que, cuando aparezca una regresión, el estado por sí solo te dé suficiente información para dirigir la solución. Y resiste la tentación de instrumentarlo todo: “Demasiados estados pueden resultar en datos demasiado granulares y en realidad pueden dificultar la interpretación del panorama general”, y existen límites superiores en la cantidad de estados para minimizar la sobrecarga (la sesión no da una cifra).1 Antes de llevarlo a producción, valida que los estados reportados coincidan con tus expectativas usando el instrumento Points of Interest.1
La cardinalidad es la misma trampa desde el lado de los metadatos. Agrupa los valores de estado que cambian rápido en categorías gruesas (pequeño, mediano, grande) en lugar de reportar conteos exactos. En el laboratorio de rendimiento de la WWDC 2026 de Apple, donde el equipo respondió preguntas en vivo sobre la adopción de MetricKit y el flujo de trabajo de energía más amplio (capturado en lo que dijo el equipo de rendimiento de Apple en el laboratorio de la WWDC26), señalaron que registrar “1.000 frente a 1.001 elementos” añade costo sin aportar información: los dos valores caen en el mismo régimen de rendimiento, así que un estado distinto para cada uno solo compra sobrecarga y nada más.4 Elige los límites que cambian el comportamiento y colapsa todo lo que hay entre ellos.
El lado de la recolección es solo la mitad del sistema. La sesión es directa al decir que “analizar las métricas a lo largo de todos los dispositivos es un problema de ciencia de datos”: levantas un servidor que ingiere los reportes, agregas a lo largo de las dimensiones que te importan, estableces una línea base y monitoreas el movimiento en cualquiera de las dos direcciones.1 Los reportes Codable y la codificación byStateReportingDomain existen para alimentar exactamente ese pipeline.
Para quienes ya lo adoptaron, la instrucción de cierre es explícita: “si estás usando la API MXMetricManager, migra a la nueva API MetricManager para aprovechar todas estas nuevas capacidades”.1 La documentación de Apple ahora formaliza esa migración: MXMetricManager está marcado como obsoleto a partir de 27.0, con la indicación “Usa MetricManager en su lugar”.2 La misma aplicación escalonada del límite de la versión 27 aterrizó en otros lugares este ciclo, incluida la obsolescencia de ImageCreator en iOS 27 de Image Playground, donde una advertencia de obsolescencia da paso a una ruptura definitiva en el lanzamiento público. Las nuevas APIs son donde vive cada avance de la sesión, y la sesión las presenta como “el futuro del framework”.1
FAQ
¿Qué cambió realmente en MetricKit en iOS 27?
El framework se reconstruyó con una API moderna y centrada en Swift. El punto de entrada es la nueva clase MetricManager; los reportes de métricas y diagnósticos llegan como streams asíncronos que puedes esperar (metricReports, diagnosticReports); los reportes son Codable para codificarlos directamente a JSON; y la estructura es navegable en código mediante intervalEntries y los grupos de métricas. iOS 27 también añade una métrica de frame rate de Metal, diagnósticos de excepciones de memoria, una category de crash que vincula los diagnósticos de crash con la contabilidad de métricas, y el framework StateReporting para métricas por estado.1
¿Cómo decide StateReporting qué métricas pertenecen a qué estado?
Tu app reporta transiciones: el estado al que se está moviendo, dentro de un dominio que tú defines. MetricKit rastrea cuánto tiempo permanece la app en cada estado y agrega los valores de métrica a lo largo del tiempo transcurrido allí. No hay pares de inicio/fin; la app simplemente reporta la condición en la que está en cualquier momento dado. Cada estado obtiene entonces su propio StateEntry en el reporte de métricas.1
¿Puedo rastrear más de una dimensión a la vez, como pantalla y rama de experimento?
Sí. Cada dominio puede contener un estado activo a la vez, pero los dominios separados corren de forma concurrente. La app de gastos de la sesión coloca la pestaña activa en un dominio y un experimento de tamaño de lote de base de datos en otro, y MetricKit entrega métricas separadas para cada pestaña y cada tamaño de lote.1
¿Debería reportar cada evento de la UI como un estado?
No. La sesión recomienda estados que representen fases estables y significativas en lugar de eventos transitorios de la UI, dominios acotados estrechamente a un área de la app, y mesura en general: demasiados estados vuelven los datos más difíciles de interpretar, y el sistema impone límites superiores en la cantidad de estados para minimizar la sobrecarga. Valida tus estados con el instrumento Points of Interest antes de llevarlos a producción.1
¿Tengo que abandonar MXMetricManager?
La orientación de la sesión es migrar de MXMetricManager a la nueva API MetricManager, porque cada nueva capacidad cubierta (streams asíncronos, reportes Codable, métricas conscientes del estado, los nuevos tipos de métrica y diagnóstico) es exclusiva del nuevo conjunto de APIs.1
MetricKit es la mitad de campo de una historia en dos partes este año: Instruments te muestra el tirón en el laboratorio, y el MetricKit consciente del estado te dice qué pantallas tienen tirones para usuarios reales, cubierto desde el lado del laboratorio en Instruments 27 y la capacidad de respuesta de las apps. El trabajo de renderizado que de verdad arregla una pestaña de 71 ms/s vive en rendimiento e interoperabilidad de SwiftUI en iOS 27. Y la razón por la que los promedios combinados engañan en primer lugar es el tema de el punto ciego del rendimiento. El hub completo de la serie es la Serie del Ecosistema Apple.
Referencias
-
Apple, WWDC 2026 session 222, Meet the new MetricKit. Source for the iOS 27 ground-up rebuild and Swift-first API framing, the
MetricManagerentry point and themetricReports/diagnosticReportsasync streams,Codablereports andJSONEncoderusage,intervalEntriesand metric groups (.cpu,.memory,.display,.gpu,peakMemory), the Metal frame rate metric, memory exception diagnostics, the crash terminationcategory, thesubmitReport()backtrace walkthrough, theStateReportingframework (domains, transition model,StateReporter,ReportableMetadata,stateEntries,byStateReportingDomainviaencodingFormatKey), 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 fromMXMetricManagertoMetricManager. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple Developer Documentation:
MXMetricManager. Marked deprecated as of 27.0, with the guidance “Use MetricManager instead.” ↩ -
Apple Developer Documentation: Reducing your app’s launch time. Launch time is measured from the moment the user taps the app icon to the first frame drawn, before the app’s process exists. ↩
-
Apple, WWDC 2026 performance group lab, session 8003. Paraphrased from a locally transcribed recording; no official transcript is published. The team advised bucketing fast-changing state metadata into coarse categories, noting that recording “1,000 versus 1,001 items” adds cost without insight. ↩