Best Practice: Bluetooth Low Energy en diferentes plataformas
Bluetooth Low Energy (BLE) es una tecnología central en los rastreadores de actividad física, dispositivos de hogar inteligente, equipos médicos y muchos otros productos IoT. Al construir una aplicación BLE, a menudo te enfrentas a una elección: iOS nativo, Flutter o React Native.
En lugar de depender de bibliotecas BLE de terceros para Flutter o React Native, el enfoque que recomiendo — y que aplico en la práctica — es escribir toda la lógica BLE en Swift nativo usando CoreBluetooth, y luego exponerla a cada framework multiplataforma mediante su mecanismo de bridge nativo. En React Native, eso significa Native Modules. En Flutter, significa Platform Channels.
Esto te da control total sobre el stack BLE, comportamiento consistente en todos tus proyectos y cero dependencia de paquetes BLE externos que puedan quedar rezagados frente a las actualizaciones del SDK de iOS.
Este artículo se centra en el lado de iOS (CoreBluetooth). Para ver la comparación de BLE en Android e iOS, consulta Best practice: iOS vs Android Bluetooth.
La Arquitectura
La idea es simple: mantener CoreBluetooth como la única fuente de verdad para BLE, y tratar React Native / Flutter como la capa de UI que se comunica con ella.
La capa nativa maneja el escaneo, la conexión, el descubrimiento de servicios y la lectura/escritura de características. La capa multiplataforma solo necesita llamar a un método o escuchar un stream de eventos.
1. La Capa BLE Nativa (CoreBluetooth)
Este código es compartido y reutilizado en todas las plataformas. Una clase BLEManager limpia encapsula toda la lógica de CoreBluetooth.
Configuración y Permisos
Agrega a Info.plist:
1 2
<key>NSBluetoothAlwaysUsageDescription</key> <string>This app uses Bluetooth to connect to your device.</string>
Para el escaneo en background, agrega también a Info.plist y activa la capability en Xcode:
// Payload grande: esperar este callback antes de enviar el siguiente chunk funcperipheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) { // Reanudar escritura por chunks } }
Este BLEManager es la base. Ahora vamos a exponerlo a cada plataforma.
2. Bridge hacia React Native
React Native se comunica con código nativo a través de Native Modules. Se crea una clase Swift que:
Hereda de RCTEventEmitter para enviar eventos (resultados de escaneo, datos) a JavaScript.
Expone métodos mediante @objc y un archivo bridge Objective-C .m.
El punto clave: la lógica BLE es idéntica en las tres plataformas. Solo difiere la capa de bridge.
5. Buenas Prácticas
Generales
Siempre filtra por service UUID al escanear. Un escaneo sin filtro devuelve todos los dispositivos visibles y consume batería.
Detén el escaneo en cuanto encuentres tu dispositivo objetivo.
Mantén una strong reference a CBPeripheral. Si el objeto es deallocated, la conexión se pierde silenciosamente.
Cachea las características después del discovery. Nunca vuelvas a descubrirlas en cada lectura/escritura.
Prueba en hardware real — CoreBluetooth no funciona en el Simulador de iOS.
Escritura de Datos
Usa .withResponse para comandos que requieren confirmación (acknowledgment).
Usa .withoutResponse para escrituras de streaming/alto rendimiento, pero siempre espera peripheralIsReady(toSendWriteWithoutResponse:) antes del siguiente chunk.
Verifica peripheral.maximumWriteValueLength(for:) antes de enviar y divide los datos según ese valor.
1 2 3 4 5 6 7 8 9
funcsend(data: Data, to peripheral: CBPeripheral, char: CBCharacteristic) { let mtu = peripheral.maximumWriteValueLength(for: .withoutResponse) var offset = 0 while offset < data.count { let end = min(offset + mtu, data.count) peripheral.writeValue(data[offset..<end], for: char, type: .withoutResponse) offset = end } }
BLE en Background
Agrega bluetooth-central a UIBackgroundModes.
Usa siempre CBCentralManagerOptionRestoreIdentifierKey para que iOS pueda re-lanzar la app y restaurar el estado tras ser terminada en background.
Implementa centralManager(_:willRestoreState:) para reconectarte al peripheral previamente conectado.
Mantén los callbacks en background breves — las tareas largas harán que el sistema mate tu app.
Específico para React Native
Siempre llama a removeAllListeners para los eventos BLE cuando el componente se desmonta, para evitar memory leaks.
En lugar de depender de bibliotecas BLE de terceros que abstraen CoreBluetooth, construir una capa Swift nativa delgada y hacer bridge de ella te ofrece:
Acceso completo a todas las funcionalidades de CoreBluetooth (state restoration, background modes, control de MTU, etc.)
Consistencia — la misma lógica BLE sirve para tu app iOS nativa, React Native y Flutter
Estabilidad — no te bloqueas por un paquete de terceros sin mantenimiento cuando sale una nueva versión de iOS
El overhead del bridge es pequeño: un archivo .m y algunos decoradores @objc para React Native, y un setup de MethodChannel + EventChannel para Flutter. A cambio, obtienes una capa BLE que tú mismo controlas y entiendes completamente.