Skip to content
← All Field Notes

CoreBluetooth

Reconnect a Bluetooth LE device on iOS

Handle lost connections, cancellation and service discovery in CoreBluetooth, then verify recovery on a real iPhone.

Oleg Yakushin

Keep connection intent separate from link state

Imagine a training session halfway through a live chart. The sensor goes out of range. Show that readings have paused, retain the session locally, and keep the device selected while the user still wants to resume. If the user taps Disconnect or ends the session, clear that intent before cancelling the connection.

Keep one central manager and a strong reference to the selected peripheral for the session. This small policy can guard a manual attempt, a scheduled retry or a radio-state change. Evaluate it on the same queue that owns your CoreBluetooth state.

import CoreBluetooth
import Foundation

struct ReconnectIntent {
    var targetID: UUID?
    var allowsReconnect = false

    func canConnect(
        peripheral: CBPeripheral,
        central: CBCentralManager
    ) -> Bool {
        allowsReconnect
            && targetID == peripheral.identifier
            && central.state == .poweredOn
            && peripheral.state == .disconnected
    }
}

Call connect(_:options:) once after the policy passes. Keep an explicit in-flight flag in the owner so multiple callbacks cannot start overlapping attempts. This example covers the retry decision; the owner still handles delegates, timers and service discovery.

DetecFit is an example of the product context: a device streams readings that feed a workout screen. The policy above is a standalone example. DetecFit case study

Handle the three connection callbacks

  1. didConnect. If the connection is still wanted, discover the services and characteristics required for this session. Enable notifications again and wait for the device handshake or a usable reading before showing the data as live. If the intent has been cleared, cancel the late connection.
  2. didDisconnectPeripheral. Clear references to services and characteristics from the old link. When the same device is still wanted and Bluetooth is on, enqueue a single reconnect attempt. A disconnect caused by the user's own cancellation follows the same intent check.
  3. didFailToConnect. End the in-flight attempt. A short, capped retry delay can help with repeated failures. Recheck intent when the delay expires and cancel any scheduled work when the user stops or changes device.

A pending CoreBluetooth connection can wait indefinitely. If the UI offers a retry deadline, cancel the pending attempt at that deadline and wait for its terminal callback before starting another. Keep one pending attempt per selected peripheral. Apple’s connection API

For a foreground flow, treat centralManagerDidUpdateState as another input. When Bluetooth is unavailable, pause retries and show the reason. When it returns, apply the same intent and in-flight checks. Validate background execution and state restoration separately if the product relies on either. Core Bluetooth documentation

Recover the data as well as the link

Track the last accepted reading per session using an identifier the device protocol actually supplies, such as a sequence number. If the firmware can replay buffered readings, request the missing range and deduplicate before writing it. If it cannot replay, preserve the gap in the saved session and show where live readings resumed.

Verify on a real iPhone

  • Walk out of range during a stream, return, and check that there is one active connection attempt and that the chart resumes after subscriptions are set up.
  • Disconnect manually while a retry is scheduled. Wait past the retry delay and confirm that no connection starts.
  • Power off the peripheral during connection, then bring it back. Check that your deadline and retry behaviour match the UI.
  • Turn off Bluetooth, turn it on again, and switch between two identical devices. Verify that the old device cannot reclaim the active session.
  • Inspect the stored record for gaps or duplicate samples. Repeat while the phone is locked if that is a supported flow.