Batería y red: el coste energético de lo que no se ve
La energía no se gasta donde uno cree. Esta lección desarma la intuición de que consumir menos es transferir menos: explica la cola de la radio y por qué el patrón de acceso pesa más que el volumen, ordena las escalas de precisión de la ubicación por coste real, sitúa el trabajo en segundo plano como una negociación con el planificador del sistema y muestra cómo medir energía sin un vatímetro.
De todos los recursos que administra una aplicación, la energía es el único que el usuario percibe con retraso, sin poder atribuirlo y con una capacidad de rencor notable. Nadie nota el instante en que una app enciende la radio celular por tercera vez en un minuto, ni el momento en que un servicio de ubicación de alta precisión empieza a alimentar el receptor de satélite de forma continua; lo que se nota es que el teléfono está caliente a media tarde y que el porcentaje de batería baja más rápido de lo normal, y el sistema ofrece una pantalla que atribuye ese consumo a una app concreta con nombre y apellidos. Ese diferimiento entre causa y efecto explica por qué el consumo energético es el problema de rendimiento peor gestionado del ecosistema: no rompe nada, no aparece en ninguna prueba, no genera un informe de fallo, y sin embargo es una de las razones más citadas para desinstalar. La buena noticia es que su física es sencilla y sus causas dominantes son pocas.
- Explicar por qué el patrón de acceso a la red pesa más que el volumen transferido.
- Ordenar las escalas de precisión de la ubicación por coste energético real.
- Negociar con el planificador del sistema en lugar de imponerle trabajo de fondo.
- Medir el consumo con
MetricKit, el medidor de energía y el estado térmico.
La cola de la radio y por qué el patrón manda
La energía es potencia multiplicada por tiempo, y en un teléfono la potencia de cada subsistema no es constante sino que sigue una máquina de estados con histéresis. La radio celular es el ejemplo canónico y el más contraintuitivo. Encenderla desde reposo cuesta bastante energía y bastante latencia, de modo que para no pagar ese arranque en cada paquete el modem no vuelve a reposo en cuanto termina una transferencia: permanece en un estado de alta potencia durante unos segundos, luego desciende a un estado intermedio, y solo después de un intervalo que ronda las decenas de segundos regresa a reposo. Ese periodo de descenso se conoce como la cola de la radio, y durante él tu app consume energía sin transferir absolutamente nada.
La consecuencia es que el coste energético de una petición no es proporcional a los bytes que mueve sino, en buena medida, a que exista. Doce peticiones pequeñas separadas por veinte segundos cada una mantienen la radio despierta durante minutos enteros y pueden costar mucho más que una única transferencia veinte veces mayor. Formulado como principio de diseño: agrupar y diferir vence a reducir. La red inalámbrica local tiene una máquina de estados análoga, con potencias menores pero la misma forma, y por eso la conclusión se mantiene con independencia del medio.
flowchart LR A[Reposo, potencia minima] --> B[Encendido, coste alto] B --> C[Transferencia activa] C --> D[Cola de alta potencia sin datos] D --> E[Estado intermedio] E --> A F[Peticion nueva durante la cola] --> C
De ahí nacen las herramientas del sistema pensadas exactamente para este problema. Una sesión discrecional permite declarar que la transferencia no es urgente y ceder al sistema la decisión de cuándo ejecutarla, con lo que se agrupa con la actividad de otros procesos y se aprovecha una radio ya encendida. La espera por conectividad evita el patrón de reintentos periódicos que mantiene el modem activo sin lograr nada. Y las banderas de red cara y de red restringida permiten respetar las decisiones del usuario sobre datos móviles y modo de datos reducidos en lugar de ignorarlas.
let configuracion = URLSessionConfiguration.background(withIdentifier: "com.ejemplo.sync")
configuracion.isDiscretionary = true // el sistema elige el momento oportuno
configuracion.waitsForConnectivity = true // en lugar de fallar y reintentar en bucle
configuracion.allowsExpensiveNetworkAccess = false // respeta datos moviles caros
configuracion.allowsConstrainedNetworkAccess = false // respeta el modo de datos reducidos
configuracion.sessionSendsLaunchEvents = true
Una consulta periódica cada treinta segundos para comprobar si hay novedades enciende la radio ciento veinte veces por hora, casi siempre para recibir la respuesta de que no hay nada. El mismo resultado con notificaciones remotas silenciosas cuesta cero mientras no ocurre nada, porque la conexión con el servicio de notificaciones ya está establecida y compartida por todo el sistema. La regla es simple: si la información la origina el servidor, que la empuje el servidor. Sondear solo se justifica cuando el origen no puede notificar, y entonces con el intervalo más largo que la función tolere.
La ubicación, el recurso más caro del catálogo
Ningún otro servicio del sistema tiene un rango de coste tan amplio ni tan mal comprendido. Pedir la máxima precisión posible mantiene activo el receptor de satélite de forma continua, y ese subsistema es, con diferencia, el mayor consumidor individual que una app puede activar; sostenerlo durante una sesión larga produce el tipo de consumo que el usuario atribuye correctamente y castiga. En el extremo opuesto, la monitorización de cambios significativos se apoya en la infraestructura celular que el teléfono ya está usando para funcionar, y su coste marginal es prácticamente nulo.
Precisión máxima continua
Receptor de satélite siempre activo. Justificado en navegación paso a paso y en registro deportivo. Devastador en cualquier otro caso.
Precisión reducida
Suficiente para el tiempo, para noticias locales o para ordenar resultados por cercanía, y con un coste que es una fracción del anterior.
Cambios significativos
Se apoya en la red celular que ya está en uso. Coste marginal casi nulo y granularidad de cientos de metros o de kilómetros.
Regiones y visitas
El sistema despierta tu app solo cuando se cruza un límite o se detecta una parada. Es el mecanismo correcto para casi toda la automatización basada en lugar.
La regla de ingeniería que se deduce es que la precisión debe pedirse como función de la tarea en curso y renegociarse cuando la tarea cambia, en lugar de configurarse una vez al arrancar. Una app de reparto necesita precisión alta mientras hay una entrega activa y cambios significativos el resto del día; una de comercio necesita precisión reducida una sola vez y nada más. Además, desde que el sistema permite al usuario conceder ubicación aproximada, pedir precisión completa cuando no hace falta no solo gasta batería sino que fuerza una petición de permiso que muchos usuarios rechazan, con lo que la función acaba deshabilitada por completo. El coste energético y el coste de privacidad apuntan aquí en la misma dirección.
Todos los recursos que hemos tratado hasta aquí se consumen mientras alguien mira: la CPU se gasta calculando algo que se va a mostrar, la memoria sostiene contenido que está en pantalla, el tiempo de arranque transcurre delante de una persona que espera. La energía rompe esa correspondencia y esa ruptura es la clave de todo el dominio, porque la mayor parte del consumo problemático ocurre cuando nadie está mirando: sincronizaciones nocturnas, ubicación de fondo, reintentos, telemetría, precarga especulativa. Ese trabajo se ejecuta en nombre del usuario pero sin que el usuario lo haya solicitado, sin que lo perciba mientras ocurre y sin que pueda relacionarlo con ninguna acción suya. La implicación no es solo técnica sino de legitimidad: cada operación de fondo es una decisión que tomas tú con un recurso que es del usuario, y el criterio para justificarla no puede ser que resulta cómoda para tu arquitectura, tiene que ser que el beneficio le llega a él de forma reconocible. Ese razonamiento explica por qué el sistema operativo no se limita a ofrecer interfaces sino que interpone un planificador entre tu intención y la ejecución: no confía en el juicio agregado de miles de aplicaciones, cada una convencida de que su sincronización es la importante, porque sabe que el óptimo local de cada una produce un pésimo global. Trabajar bien en este dominio consiste en dejar de pelear contra ese planificador —buscando trucos para ejecutar más a menudo, encadenando tareas, abusando de mecanismos pensados para otra cosa— y empezar a cooperar con él: declarar la urgencia real, admitir la flexibilidad temporal, aceptar la caducidad. La app que declara honestamente sus necesidades recibe planificación favorable de forma sostenida; la que intenta forzar el sistema acaba con su presupuesto de fondo recortado por un heurístico que ha aprendido a desconfiar de ella.
Trabajo en segundo plano como negociación
El sistema no ejecuta tu tarea de fondo cuando tú quieres, sino cuando le conviene al conjunto, y decide con un modelo que aprende de la conducta del usuario: a qué horas abre tu app, con qué frecuencia, si el dispositivo está cargando, si hay red inalámbrica, cuál es el estado térmico y si el modo de bajo consumo está activo. Presentado así, el diseño correcto deja de ser una cuestión de intervalos y pasa a ser una cuestión de declaración de intenciones.
import BackgroundTasks
// Refresco corto y frecuente: actualizar contenido ligero
let refresco = BGAppRefreshTaskRequest(identifier: "com.ejemplo.refresco")
refresco.earliestBeginDate = Date(timeIntervalSinceNow: 3600)
// Procesamiento largo: mantenimiento, indexado, limpieza
let proceso = BGProcessingTaskRequest(identifier: "com.ejemplo.mantenimiento")
proceso.requiresNetworkConnectivity = false
proceso.requiresExternalPower = true // el sistema lo ejecutara mientras carga
try? BGTaskScheduler.shared.submit(refresco)
try? BGTaskScheduler.shared.submit(proceso)
Dos detalles distinguen una implementación robusta de una que se abandona. El primero es el manejador de expiración: el sistema concede un presupuesto de tiempo acotado y, si se agota, exige que la tarea se detenga de inmediato; no hacerlo cuenta como incumplimiento y penaliza la planificación futura. El segundo es la idempotencia: como el instante de ejecución es impredecible y la tarea puede interrumpirse a medias, el trabajo tiene que poder repetirse sin duplicar efectos.
Hay además una decisión de diseño previa que ahorra más energía que cualquier ajuste: preguntarse si la tarea de fondo debe existir. Una parte considerable del trabajo que las apps programan en segundo plano existe para que el contenido esté fresco en el instante improbable en que el usuario abra la app, y su valor real es que la pantalla inicial aparezca poblada medio segundo antes. Comparado con el coste de despertar el proceso, encender la radio y sincronizar varias veces al día durante meses, ese beneficio rara vez sale a cuenta. La alternativa honesta suele ser cargar al abrir, mostrando lo que hay en cache mientras llega lo nuevo.
Conviene además leer el contexto antes de decidir cuánto trabajo hacer. El modo de bajo consumo es una declaración explícita del usuario de que prefiere autonomía a funcionalidad, y respetarlo significa reducir la frecuencia de sincronización, desactivar la precarga especulativa y bajar la precisión de ubicación. El estado térmico es la señal complementaria: cuando el sistema entra en niveles serios de calentamiento ya está limitando la frecuencia del procesador, y seguir empujando trabajo solo empeora el rendimiento de todo el dispositivo.
let info = ProcessInfo.processInfo
if info.isLowPowerModeEnabled || info.thermalState == .serious {
sincronizador.reducirCadencia()
precarga.desactivar()
}
Medir energía sin un vatímetro
La objeción habitual es que el consumo no se puede medir sin instrumentación de hardware, y es falsa desde hace años. MetricKit entrega a diario tres familias de datos que bastan para gobernar el dominio: tiempo de CPU acumulado, bytes transferidos en celular y en red inalámbrica separados por dirección, y tiempo de actividad de ubicación desglosado por escala de precisión. Esa última métrica es especialmente reveladora porque suele destapar que una app pasa horas al día con precisión alta activa sin que nadie del equipo lo supiera.
import MetricKit
func didReceive(_ payloads: [MXMetricPayload]) {
for carga in payloads {
registrar(cpu: carga.cpuMetrics?.cumulativeCPUTime)
registrar(red: carga.networkTransferMetrics?.cumulativeCellularUpload)
registrar(ubicacion: carga.locationActivityMetrics?.cumulativeBestAccuracyTime)
}
}
En desarrollo, el medidor de energía de Xcode ofrece una lectura relativa e inmediata mientras se usa la app, y el registro energético de Instruments desglosa por subsistema con suficiente resolución para atribuir un pico a la radio, a la ubicación o al procesador. Y el organizador de Xcode agrega en producción el consumo y la escritura en disco por versión, que es la vista adecuada para detectar la regresión que introdujo una función nueva.
Merece una mención aparte esa última métrica, la escritura en disco, porque suele sorprender que aparezca en un panel de consumo. La memoria flash de un teléfono gasta energía al escribir mucho más que al leer, y además el sistema de archivos con registro por diario multiplica cada escritura pequeña por su contabilidad asociada. Una app que persiste el estado en cada pulsación, que registra telemetría con una escritura por evento o que reconstruye su cache en disco a cada rato aparece en ese panel sin haber tocado la radio ni la ubicación. El remedio es el mismo principio de agrupación que rige la red: acumular en memoria y volcar por lotes, con un volcado forzado al pasar a segundo plano para no perder datos.
Cuando ProcessInfo informa de un estado térmico serio o crítico, el sistema ya ha empezado a limitar la frecuencia del procesador, a reducir el ritmo de refresco de la pantalla y a atenuar la retroiluminación. Es decir, no es una advertencia sobre lo que podría pasar sino la constatación de que tu rendimiento ya está degradado. Tratarlo como una señal de entrada —reduciendo cadencia, cancelando precarga, bajando precisión— es lo único que puede sacarte de ese régimen. Ignorarlo produce el peor de los mundos: más consumo y menos rendimiento a la vez.
La energía es potencia por tiempo, y cada subsistema tiene histéresis: la radio permanece en alta potencia decenas de segundos después de la última transferencia, así que el patrón de acceso pesa más que el volumen. Agrupa y difiere antes que reducir; sustituye el sondeo por notificaciones. La ubicación tiene el rango de coste más amplio del catálogo y su precisión debe renegociarse por tarea, no configurarse al arrancar. El trabajo de fondo es una negociación con el planificador: declara la urgencia real y respeta el modo de bajo consumo y el estado térmico.
- Registra cuántas peticiones de red hace tu app en cinco minutos de uso normal y cuántas de ellas podrían agruparse o diferirse con una sesión discrecional.
- Sustituye cualquier sondeo periódico por notificaciones silenciosas o, si no es posible, multiplica el intervalo por cuatro y mide la diferencia.
- Audita todas las peticiones de ubicación de tu código y baja cada una a la escala mínima que su función tolere.
- Comprueba que tus tareas de fondo implementan el manejador de expiración y son idempotentes; provoca una expiración a propósito y observa el resultado.
- Recoge un mes de métricas de ubicación y de red con
MetricKity busca el subsistema que consume más de lo que su valor para el usuario justifica.