En el vasto universo de la programación para macOS, a menudo nos encontramos con la necesidad de que nuestras aplicaciones reaccionen a eventos específicos del sistema. Uno de esos eventos, sorprendentemente útil y a veces pasado por alto, es el estado de la fuente de alimentación de nuestro Mac. ¿No sería fantástico que tu aplicación supiera cuándo tu ordenador está enchufado o desconectado, y pudiera actuar en consecuencia? Imagina automatizar tareas, optimizar el uso de la batería o simplemente mostrar un aviso amigable. En este artículo, vamos a desentrañar los secretos de IOKit y IOPowerSources en Objective-C para construir un método robusto que te mantenga informado sobre el estado de la energía de tu Mac. ¡Prepárate para añadir una capa de inteligencia a tus aplicaciones!
¿Por Qué es Relevante Saber si tu Mac Está Enchufado? 🤔
Antes de sumergirnos en el código, reflexionemos un momento sobre la utilidad de esta funcionalidad. A primera vista, podría parecer un detalle menor, pero las aplicaciones prácticas son numerosas:
- Optimización de Tareas: ¿Tienes procesos intensivos (copias de seguridad, renderizado de vídeo, compilaciones largas) que preferirías que solo se ejecutaran cuando el Mac está conectado a la corriente para evitar agotar la batería?
- Gestión de Descargas: Pausa o reanuda grandes descargas automáticamente dependiendo de si estás usando la batería o la corriente.
- Salud de la Batería: Recibe recordatorios para desenchufar tu Mac cuando la batería esté completamente cargada, o para conectarlo si el nivel es bajo, contribuyendo a una mayor vida útil de la misma.
- Modo de Ahorro de Energía: Cambia automáticamente el perfil de rendimiento o la configuración de brillo para ahorrar energía cuando el Mac funciona con batería.
- Notificaciones Personalizadas: Simplemente, muestra un pequeño aviso en la pantalla o envía una notificación push cuando el estado de la energía cambie.
Como ves, las posibilidades son amplias. Nuestro objetivo es proporcionarte la base técnica para que puedas implementar cualquiera de estas ideas.
La Clave: El Framework IOKit y IOPowerSources 💻
macOS, como sistema operativo avanzado que es, ofrece un conjunto de herramientas extremadamente potente para interactuar con el hardware: el framework IOKit. Dentro de IOKit, hay una subclase específica que nos interesa: IOPowerSources. Este framework nos permite acceder a información detallada sobre las fuentes de energía del sistema, incluyendo baterías, adaptadores de corriente y otros dispositivos de alimentación.
No te preocupes si IOKit suena intimidante; para nuestro propósito, solo necesitaremos unas pocas funciones clave que lo hacen sorprendentemente accesible.
Paso 1: Obtener la Información Actual de la Fuente de Energía 🔋
Lo primero es poder saber en un instante si el Mac está actualmente enchufado. Para ello, utilizaremos la función IOPMCopyActivePowerSourcesInfo()
, que devuelve un diccionario (CFDictionaryRef
) con toda la información relevante.
„`objective-c
#import
#import
– (BOOL)isMacCurrentlyPluggedIn {
CFTypeRef powerSourcesInfo = IOPMCopyActivePowerSourcesInfo();
if (powerSourcesInfo) {
// Obtenemos un array de las fuentes de energía activas
CFArrayRef powerSourcesList = CFArrayGetValueAtIndex(powerSourcesInfo, 0); // Asumiendo que el primer elemento es el array
if (powerSourcesList && CFArrayGetCount(powerSourcesList) > 0) {
CFDictionaryRef powerSource = CFArrayGetValueAtIndex(powerSourcesList, 0); // Tomamos la primera fuente de energía
// Verificamos si está cargando (conectado a la corriente)
CFBooleanRef isCharging = CFDictionaryGetValue(powerSource, kIOPSIsChargingKey);
CFRelease(powerSourcesInfo); // Liberar la memoria
return (isCharging == kCFBooleanTrue);
}
CFRelease(powerSourcesInfo); // Liberar la memoria incluso si no hay fuentes
}
return NO; // No se pudo obtener la información o no hay fuentes de energía
}
„`
Analicemos brevemente este fragmento. IOPMCopyActivePowerSourcesInfo()
nos da una referencia a un objeto que contiene los detalles de la energía. Luego, extraemos el array de fuentes de energía y examinamos la primera (que generalmente será la batería o el adaptador principal). El valor `kIOPSIsChargingKey` en el diccionario nos indica si el sistema está recibiendo energía del adaptador.
Paso 2: Monitorización Dinámica – ¡Reaccionando a los Cambios! ⚡
Una comprobación única es útil, pero para un aviso real, necesitamos que nuestra aplicación se entere *cuando* el estado de la conexión cambie. Aquí es donde entran en juego las notificaciones del sistema. IOKit nos permite registrar una función de callback que se llamará cada vez que haya un evento relevante de la fuente de energía.
Para esto, utilizaremos IOPSCreatePowerSourceEventSource()
y lo añadiremos al NSRunLoop
de nuestra aplicación.
Definición de una Clase para la Monitorización (PowerMonitor.h)
Es buena práctica encapsular esta lógica en su propia clase. Crearemos una clase `PowerMonitor` que se encargará de gestionar la monitorización y notificar a otros objetos de los cambios.
„`objective-c
// PowerMonitor.h
#import
NS_ASSUME_NONNULL_BEGIN
extern NSString *const PowerMonitorDidChangePowerSourceNotification;
@interface PowerMonitor : NSObject
@property (nonatomic, assign, readonly) BOOL isPluggedIn;
+ (instancetype)sharedMonitor; // Singleton para fácil acceso
– (void)startMonitoring;
– (void)stopMonitoring;
@end
NS_ASSUME_NONNULL_END
„`
Aquí definimos un NSString
para el nombre de la notificación que enviaremos, una propiedad para el estado actual de la conexión y un método singleton para acceder fácilmente a la instancia del monitor. Los métodos `startMonitoring` y `stopMonitoring` gestionarán el ciclo de vida de nuestra monitorización.
Implementación de la Clase (PowerMonitor.m)
Ahora, la parte más jugosa: la implementación.
„`objective-c
// PowerMonitor.m
#import „PowerMonitor.h”
#import
#import
#import
NSString *const PowerMonitorDidChangePowerSourceNotification = @”PowerMonitorDidChangePowerSourceNotification”;
// Función de callback que será llamada por IOKit
void PowerSourceNotificationCallback(void *context) {
// El contexto es nuestra instancia de PowerMonitor
PowerMonitor *monitor = (__bridge PowerMonitor *)context;
// Llamamos a un método de instancia para procesar el cambio
[monitor _powerSourceChanged];
}
@interface PowerMonitor () {
CFRunLoopSourceRef _powerSourceEventSource;
BOOL _currentIsPluggedIn;
}
@end
@implementation PowerMonitor
#pragma mark – Singleton
+ (instancetype)sharedMonitor {
static PowerMonitor *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
– (instancetype)init {
self = [super init];
if (self) {
_currentIsPluggedIn = [self _checkCurrentPowerStatus];
}
return self;
}
#pragma mark – Public Methods
– (void)startMonitoring {
if (_powerSourceEventSource == NULL) {
// Crear la fuente de eventos de la fuente de energía
_powerSourceEventSource = IOPSCreatePowerSourceEventSource(kCFAllocatorDefault, PowerSourceNotificationCallback, (__bridge void *)(self));
if (_powerSourceEventSource) {
// Añadir la fuente al bucle de eventos principal (main run loop)
CFRunLoopAddSource(CFRunLoopGetMain(), _powerSourceEventSource, kCFRunLoopCommonModes);
NSLog(@”✅ PowerMonitor: Monitoreo de fuentes de energía iniciado.”);
} else {
NSLog(@”⚠️ PowerMonitor: Fallo al crear la fuente de eventos de la fuente de energía.”);
}
} else {
NSLog(@”💡 PowerMonitor: El monitoreo ya está activo.”);
}
}
– (void)stopMonitoring {
if (_powerSourceEventSource) {
// Remover la fuente del run loop
CFRunLoopRemoveSource(CFRunLoopGetMain(), _powerSourceEventSource, kCFRunLoopCommonModes);
// Liberar la fuente
CFRelease(_powerSourceEventSource);
_powerSourceEventSource = NULL;
NSLog(@”🚫 PowerMonitor: Monitoreo de fuentes de energía detenido.”);
} else {
NSLog(@”💡 PowerMonitor: El monitoreo no estaba activo.”);
}
}
– (BOOL)isPluggedIn {
return _currentIsPluggedIn;
}
#pragma mark – Private Methods
– (BOOL)_checkCurrentPowerStatus {
CFTypeRef powerSourcesInfo = IOPMCopyActivePowerSourcesInfo();
BOOL pluggedIn = NO;
if (powerSourcesInfo) {
CFArrayRef powerSourcesList = CFArrayGetValueAtIndex(powerSourcesInfo, 0);
if (powerSourcesList && CFArrayGetCount(powerSourcesList) > 0) {
CFDictionaryRef powerSource = CFArrayGetValueAtIndex(powerSourcesList, 0);
CFBooleanRef isCharging = CFDictionaryGetValue(powerSource, kIOPSIsChargingKey);
pluggedIn = (isCharging == kCFBooleanTrue);
}
CFRelease(powerSourcesInfo);
}
return pluggedIn;
}
– (void)_powerSourceChanged {
BOOL newPluggedInStatus = [self _checkCurrentPowerStatus];
if (newPluggedInStatus != _currentIsPluggedIn) {
_currentIsPluggedIn = newPluggedInStatus;
NSLog(@”🔌 PowerMonitor: El estado de la energía ha cambiado. ¿Enchufado? %@”, newPluggedInStatus ? @”Sí” : @”No”);
// Enviamos una notificación para que otros objetos puedan reaccionar
[[NSNotificationCenter defaultCenter] postNotificationName:PowerMonitorDidChangePowerSourceNotification
object:self
userInfo:@{@”isPluggedIn”: @(newPluggedInStatus)}];
}
}
// Asegúrate de liberar la fuente de eventos si la instancia se desaloja
– (void)dealloc {
[self stopMonitoring];
}
@end
„`
Hemos definido una función C pura (`PowerSourceNotificationCallback`) que IOKit invocará. Esta función simplemente delega la llamada a un método de instancia (`_powerSourceChanged`) de nuestro `PowerMonitor` actual, pasando la instancia a través del parámetro `context`. Esto nos permite mantener la lógica de Objective-C dentro de nuestra clase.
El método `startMonitoring` crea el `CFRunLoopSourceRef` y lo añade al CFRunLoopGetMain()
, asegurando que nuestra callback se ejecute en el hilo principal. Cuando se invoca `_powerSourceChanged`, volvemos a comprobar el estado actual y, si ha habido un cambio, actualizamos nuestra propiedad y, crucialmente, enviamos una NSNotification
. Esta es la forma estándar en Cocoa de comunicar eventos entre objetos desacoplados.
Paso 3: Usando el Monitor en tu Aplicación 🚀
Ahora que tenemos nuestro robusto `PowerMonitor`, ¿cómo lo usamos? Es sencillo. Por ejemplo, en tu `AppDelegate` o en el controlador de vista principal:
„`objective-c
// AppDelegate.m o en cualquier clase donde quieras usarlo
#import „AppDelegate.h”
#import „PowerMonitor.h” // Importa tu clase PowerMonitor
@implementation AppDelegate
– (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// 1. Iniciar la monitorización
[[PowerMonitor sharedMonitor] startMonitoring];
// 2. Registrarse para recibir notificaciones de cambios
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handlePowerSourceChange:)
name:PowerMonitorDidChangePowerSourceNotification
object:nil];
// Comprobar el estado inicial
BOOL initialStatus = [[PowerMonitor sharedMonitor] isPluggedIn];
NSLog(@”🔌 Estado inicial del Mac: ¿Enchufado? %@”, initialStatus ? @”Sí” : @”No”);
}
– (void)handlePowerSourceChange:(NSNotification *)notification {
NSDictionary *userInfo = notification.userInfo;
BOOL isPluggedIn = [userInfo[@”isPluggedIn”] boolValue];
// Aquí es donde tu aplicación reacciona al cambio
if (isPluggedIn) {
NSLog(@”🎉 ¡El Mac acaba de ser enchufado! Es un buen momento para iniciar esa copia de seguridad.”);
// Por ejemplo, aquí podrías iniciar una tarea intensiva
} else {
NSLog(@”🔋 ¡El Mac acaba de ser desenchufado! Quizás quieras pausar ciertas operaciones para ahorrar batería.”);
// Por ejemplo, aquí podrías reducir el brillo de la pantalla o pausar descargas
}
}
– (void)applicationWillTerminate:(NSNotification *)aNotification {
// 3. Detener la monitorización y remover el observador cuando la aplicación termine
[[PowerMonitor sharedMonitor] stopMonitoring];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:PowerMonitorDidChangePowerSourceNotification
object:nil];
}
@end
„`
¡Y listo! Con estas pocas líneas, tu aplicación ahora tiene la capacidad de reaccionar inteligentemente al estado de la energía de tu Mac. Cuando conectes o desconectes el cable de corriente, verás los mensajes en la consola y podrás ejecutar tu lógica personalizada.
Opinión Basada en Datos Reales: Cuidando la Batería de tu Mac 💚
Más allá de la automatización, esta capacidad de monitorización nos brinda una herramienta valiosa para la salud a largo plazo de la batería de nuestro Mac. Las baterías de iones de litio modernas, aunque robustas, tienen una vida útil que se ve afectada por ciertos patrones de carga.
„Mantener la batería constantemente al 100% de carga o dejarla descargarse completamente hasta el 0% durante largos períodos puede acelerar su degradación. Los expertos recomiendan, para una óptima longevidad, mantener la batería entre el 20% y el 80% de su capacidad la mayor parte del tiempo.”
Al saber cuándo tu Mac está enchufado y cuál es su nivel de batería (información que también puedes obtener de IOKit con `kIOPSCurrentCapacityKey` y `kIOPSMaxCapacityKey`), puedes crear un sistema que te avise cuando la batería alcance el 80% para desenchufar, o cuando baje del 20% para conectar. Esto es especialmente útil para usuarios que trabajan con sus laptops conectadas a la corriente durante jornadas enteras, una práctica común pero no ideal para la salud de la batería a largo plazo. Tu aplicación puede convertirse en un „entrenador” personal para la batería de tu Mac.
Consideraciones Adicionales y Mejoras 🛠️
- Seguridad del Hilo (Thread Safety): Aunque nuestro callback se añade al `main run loop` y se ejecuta en el hilo principal, siempre es bueno ser consciente de que las llamadas a IOKit son de bajo nivel. En sistemas más complejos o con múltiples hilos, asegúrate de que cualquier acceso a variables compartidas sea seguro.
- Error Handling: Hemos incluido algunas comprobaciones básicas (`if (powerSourcesInfo)`), pero en una aplicación de producción, querrás añadir un manejo de errores más exhaustivo, por ejemplo, si `IOPSCreatePowerSourceEventSource` falla.
- Notificaciones al Usuario: En lugar de solo `NSLog`, puedes usar `NSUserNotificationCenter` (para macOS 10.8-10.14) o `UNUserNotificationCenter` (para macOS 10.14+) para mostrar avisos visibles al usuario cuando el estado de la energía cambie.
- Más Información de la Batería: IOKit puede darte mucha más información: `kIOPSCurrentCapacityKey` (carga actual), `kIOPSMaxCapacityKey` (capacidad máxima), `kIOPSTimeToEmptyKey` (tiempo restante de batería), etc. Explora el framework para enriquecer tus avisos.
Conclusión ✨
Hemos recorrido un camino fascinante, desde la motivación hasta la implementación práctica, para crear un método en Objective-C que nos notifique sobre los cambios en el estado de la energía de nuestro Mac. Hemos explorado el poderoso framework IOKit y, más específicamente, IOPowerSources, para obtener y monitorizar esta información vital.
La belleza de macOS reside en la riqueza de sus APIs, que permiten a los desarrolladores ir más allá de lo superficial y crear experiencias verdaderamente integradas y útiles. Este pequeño proyecto no solo te proporciona una funcionalidad específica, sino que también te abre la puerta a un mundo de interacción con el hardware de tu Mac. Experimenta, construye sobre esta base y deja que tu creatividad te guíe para hacer tus aplicaciones aún más inteligentes y responsivas.
¡Espero que este artículo te haya sido de gran utilidad y te inspire a seguir explorando las profundidades de la programación en Objective-C para macOS! ¡Feliz codificación! 🚀