CoreBluetooth in Production: Lessons Learned
Building reliable BLE connections in real-world apps
Introduction
After shipping three production apps with CoreBluetooth integration, I've learned that building reliable Bluetooth Low Energy (BLE) connections is far more challenging than the documentation suggests. This article shares the hard-won lessons and patterns that actually work in production.
The Reality of BLE Development
Apple's CoreBluetooth framework is powerful, but it's also unforgiving. Connection drops, state management issues, and cryptic errors are common. Here's what you need to know to build robust BLE features.
Common Challenges
- Unreliable Connections: BLE connections drop frequently in real-world conditions
- State Management: Tracking peripheral state across app lifecycle is complex
- Background Modes: Limited functionality when app is backgrounded
- Error Handling: Vague error messages that don't indicate root cause
Architecture Pattern: The BLE Manager
Don't scatter Bluetooth code throughout your app. Create a centralized manager that handles all BLE operations:
import CoreBluetooth import Combine class BLEManager: NSObject, ObservableObject { @Published var isScanning = false @Published var discoveredPeripherals: [CBPeripheral] = [] @Published var connectedPeripheral: CBPeripheral? @Published var connectionState: ConnectionState = .disconnected private var centralManager: CBCentralManager! private var reconnectTimer: Timer? private let serviceUUID = CBUUID(string: "YOUR-SERVICE-UUID") enum ConnectionState { case disconnected case connecting case connected case failed(Error) } override init() { super.init() centralManager = CBCentralManager(delegate: self, queue: .main, options: [CBCentralManagerOptionShowPowerAlertKey: true]) } }
Lesson 1: Always Implement Auto-Reconnect
BLE connections will drop. Accept this reality and build automatic reconnection:
extension BLEManager { func startAutoReconnect(to peripheral: CBPeripheral) { reconnectTimer?.invalidate() reconnectTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in guard let self = self, peripheral.state != .connected else { return } print("Attempting reconnection...") self.centralManager.connect(peripheral, options: nil) } } }
Conclusion
CoreBluetooth development requires defensive programming and thorough testing. The patterns shared here have proven reliable across multiple production apps with thousands of users.
Got questions about BLE development? Drop them in the comments or reach out on Twitter!