Core Bluetooth for Fitness Devices on iOS (2026)
Last verified August 14, 2026 · 5 min read
Covered here:HealthKit
Core Bluetooth is a small API surface with a few sharp edges, and most of the pain teams hit on iOS comes from three of them: a missing Info.plist key, an attempt to make the framework testable by subclassing it, and a device layer that has spread through the codebase so that nothing downstream can run without hardware in the room. All three are avoidable on day one.
What the framework covers#
Apple's abstract for Core Bluetooth is precise about scope: "Communicate with Bluetooth low energy and BR/EDR ('Classic') Devices." For fitness work you are almost entirely in Bluetooth Low Energy territory, because the sensors and machines you care about publish SIG-standard GATT services — Heart Rate (0x180D), Cycling Power (0x1818), Cycling Speed and Cadence (0x1816), Running Speed and Cadence (0x1814), and the Fitness Machine Service (0x1826).
That is worth internalizing before you write anything: the interesting part of your integration is the profile, not the framework. Core Bluetooth is the transport. The knowledge that transfers between iOS, Android and the browser lives in the profile documents, and we point at the SIG specifications for wire formats rather than restating them.
Central or peripheral#
Apple describes two roles. CBCentralManager handles the central side: scanning, connecting, and managing discovered peripherals. CBPeripheralManager handles the peripheral side, advertising local services from your own device.
For a fitness app reading a strap, a power meter or a treadmill, you are the central and the sensor is the peripheral. CBCentralManager is where you will spend your time. CBPeripheralManager becomes relevant only if your app is the thing being read — a phone pretending to be a sensor, which is occasionally useful in test rigs and rarely what a consumer product needs.
Getting the role right at the start matters because the two APIs are not interchangeable and a team that starts on the wrong one usually notices late.
The Info.plist key, and Apple's warning about it#
From iOS 13 onward, accessing Core Bluetooth APIs requires NSBluetoothAlwaysUsageDescription in your Info.plist. On iOS 12 and earlier the key was NSBluetoothPeripheralUsageDescription.
Apple's sentence about what happens if you skip it is blunt: "Your app will crash if its Info.plist doesn't include usage description keys for the types of data it needs to access."
This is the single most common first-run failure in a new BLE integration, and it has a nasty property: it is invisible in code review, because nothing in the Swift file is wrong. The check belongs in your build, not in your memory. Add an assertion in a test or a build-phase script that the key is present and non-empty, because a plist merge, a target duplication or a fresh app extension can drop it long after somebody first added it.
Write the string for the user, not for the reviewer. "Connects to your heart rate strap and gym equipment" tells someone what they are agreeing to; a sentence about Bluetooth peripherals does not.
Do not subclass#
Apple states it without qualification: "Don't subclass any of the classes of the Core Bluetooth framework. Overriding these classes isn't supported and results in undefined behavior."
That closes off the shortcut people reach for when they want a testable BLE layer, and it closes it for the right reason. A subclassed central manager would still be a real central manager holding a real connection to the Bluetooth stack, so you would be fighting the framework to produce a fake that is not actually fake. The supported route is to define your own protocol and let a real Core Bluetooth implementation sit behind it, which is where the next section goes.
One platform caveat#
Background modes are not supported in iPad apps running on macOS. If your product ships as an iPad app that also runs on Mac and you were relying on background Bluetooth behavior, that is a documented limitation to design around rather than a bug to file.
Keep the framework in one thin layer#
This is judgement, not Apple's advice, but it is the structural decision that decides how much of your product can be tested and how much of it can be reused.
Define a protocol that expresses what your app asks of a device, and nothing wider. Not "scan," "discover services," "discover characteristics," "set notify" — those are Core Bluetooth's vocabulary, and a protocol that mirrors them has bought you an extra type and nothing else. Express your own questions instead: connect to a heart rate source, stream heart rate readings, stop. The adapter behind the protocol is the only file that imports CoreBluetooth.
Everything downstream — smoothing, zone calculation, interval detection, session assembly, whatever your product actually does — takes your domain types and can be driven by recorded data with no radio involved. That is what makes testing BLE fitness device integrations possible at all, and it mirrors the seam pattern used for camera features in testing camera features without a device.
The rule of thumb: put the seam at the point where the last CB type disappears from your call graph, and push that point as early as you can. Devices, connection state and characteristic notifications should turn into your own types within a few lines of arriving.
Where the data goes afterward#
A BLE session that only lives in memory is half a product. Live readings usually need to end up somewhere durable and, on iOS, usually in HealthKit — a separate integration with its own permissions model, covered in integrating HealthKit. If your app is also reading wrist heart rate, live heart rate from Apple Watch explains why that path is a workout session rather than a store query, and why the two can end up delivering overlapping data for the same minutes. Reconciling overlapping sources is its own problem; deduplicating health data is where that lives.
Frequently asked questions
- Which Info.plist key does Core Bluetooth require on iOS 13 and later?
- NSBluetoothAlwaysUsageDescription. It is required to access Core Bluetooth APIs from iOS 13 onward; iOS 12 and earlier used NSBluetoothPeripheralUsageDescription. Apple's warning is unambiguous: your app will crash if its Info.plist doesn't include usage description keys for the types of data it needs to access. Because nothing in your Swift code looks wrong when the key is missing, this is worth enforcing mechanically rather than remembering. Add a test or a build-phase check that asserts the key exists and is non-empty, since plist merges, duplicated targets and new app extensions all drop it easily. Write the description for the user, naming the devices you connect to.
- Should an iOS fitness app use CBCentralManager or CBPeripheralManager for a heart rate strap?
- CBCentralManager. Apple describes the central role as scanning for, connecting to, and managing peripherals, which is exactly what an app reading a strap, a power meter or a treadmill does: the sensor is the peripheral and your app is the central. CBPeripheralManager is the opposite role, used to advertise local services from the device your code runs on, so it applies when the phone is the thing being read rather than the thing reading. That is occasionally useful in a test rig and rarely what a consumer fitness product needs. The two APIs are not interchangeable, so pick the role deliberately before writing the connection layer.
- Can I subclass CBCentralManager or CBPeripheral to make my BLE fitness layer testable?
- No, and Apple says so directly: don't subclass any of the classes of the Core Bluetooth framework, because overriding these classes isn't supported and results in undefined behavior. The instinct is understandable, since a fake central manager looks like the easy route to a testable device layer, but a subclass would still be a real manager attached to the real Bluetooth stack. The supported approach is to define your own narrow protocol expressing what your app asks of a device, put a Core Bluetooth adapter behind it as the only file importing the framework, and let everything downstream run against recorded data with no radio involved.
Keep reading
Elsewhere on the site
Pages that share this one’s concepts and sources, from other sections.
Next steps
Was this page useful?
Independent comparison, last reviewed August 14, 2026. Pricing, rate limits, and feature availability change often — confirm current details in each provider’s official documentation before you commit. Product and company names are trademarks of their respective owners; AIFitnessAPI is not affiliated with, endorsed by, or sponsored by any product listed here.
← All connected devices · by AIFitnessAPI