CoreBluetooth 入门:扫描并连接 BLE 手环实战

目录
-
为什么选择 BLE?
-
前置准备
- 权限与 Capability
- Info.plist 必填键
-
CoreBluetooth 核心角色
-
创建 CBCentralManager
-
扫描附近设备
-
连接与断开
-
发现服务 / 特征
-
读写数据与订阅通知
-
重连、后台与省电策略
-
常见错误排查清单
-
结语
1. 为什么选择 BLE?
- 超低功耗:手环、心率带等穿戴设备必须长续航。
- 广播+连接双模式:无需事先配对,即可发现目标再建立安全会话。
- 跨平台标准:遵循 Bluetooth 4.0+ GATT 规范,Android 与 iOS 均可互通。
2. 前置准备
2.1 打开 Capability
Xcode > Signing & Capabilities > +Capability > Background Modes → 勾选 Bluetooth LE Accessories(如需后台扫描/连接)和 Uses Bluetooth LE accessories.
2.2 Info.plist 键值
<key>NSBluetoothPeripheralUsageDescription</key>
<string>App 需要蓝牙权限以连接您的手环</string>
<key>UIBackgroundModes</key>
<array><string>bluetooth-central</string>
</array>
iOS 17 起,蓝牙权限提示分级更细;描述语必须说明具体用途,否则审核会被拒。
3. CoreBluetooth 核心角色
| 角色 | 作用 |
|---|---|
| CBCentralManager | 扫描、连接、管理外围设备 |
| CBPeripheral | 代表单个外围 (手环) |
| CBService | 功能模块(如心率服务) |
| CBCharacteristic | 数据通道(读/写/通知) |
所有交互都通过 委托(delegate)异步回调,无阻塞主线程。
4. 创建 CBCentralManager
import CoreBluetoothfinal class BLECenter: NSObject {private lazy var central = CBCentralManager(delegate: self,queue: .main, // UI 友好options: [CBCentralManagerOptionShowPowerAlertKey: true])
}// MARK: - CBCentralManagerDelegate
extension BLECenter: CBCentralManagerDelegate {func centralManagerDidUpdateState(_ central: CBCentralManager) {guard central.state == .poweredOn else { return } // 需蓝牙已开启startScanning()}
}
要点
central.state值必须精确判断,.poweredOn之前任何操作都无效。- 建议持有单例,避免多个 CBCentralManager 实例争抢硬件资源。
5. 扫描附近设备
private func startScanning() {// 仅侦听包含「通用心率服务」的设备,可降低功耗let heartRateServiceUUID = CBUUID(string: "180D")central.scanForPeripherals(withServices: [heartRateServiceUUID],options: [CBCentralManagerScanOptionAllowDuplicatesKey: false])
}// 回调:发现新外设
func centralManager(_ central: CBCentralManager,didDiscover peripheral: CBPeripheral,advertisementData: [String : Any],rssi RSSI: NSNumber) {print("发现 \(peripheral.name ?? "未知") @\(RSSI)dB")// 记录并发起连接self.targetPeripheral = peripheralcentral.stopScan()central.connect(peripheral, options: nil)
}
- RSSI:信号强度,数值越接近 0 越近。
- AllowDuplicatesKey:禁止重复回调,省电。
6. 连接与断开
func centralManager(_ central: CBCentralManager,didConnect peripheral: CBPeripheral) {peripheral.delegate = selfperipheral.discoverServices(nil) // 拉取所有服务
}func centralManager(_ central: CBCentralManager,didFailToConnect peripheral: CBPeripheral,error: Error?) {print("连接失败:\(error?.localizedDescription ?? "未知")")
}func centralManager(_ central: CBCentralManager,didDisconnectPeripheral peripheral: CBPeripheral,error: Error?) {// 保证 UI 状态同步;如需自动重连,可在此处重新 connect
}
iOS 自动维护安全配对;若设备要求「配对码」,系统会弹出原生对话框,无需额外逻辑。
7. 发现服务 / 特征
func peripheral(_ peripheral: CBPeripheral,didDiscoverServices error: Error?) {guard error == nil else { return }peripheral.services?.filter { $0.uuid == CBUUID(string: "180D") } // 心率服务.forEach { peripheral.discoverCharacteristics(nil, for: $0) }
}func peripheral(_ peripheral: CBPeripheral,didDiscoverCharacteristicsFor service: CBService,error: Error?) {guard error == nil else { return }for characteristic in service.characteristics ?? [] {switch characteristic.properties {case _ where characteristic.properties.contains(.notify):peripheral.setNotifyValue(true, for: characteristic)case _ where characteristic.properties.contains(.read):peripheral.readValue(for: characteristic)default:break}}
}
8. 读写数据与订阅通知
func peripheral(_ peripheral: CBPeripheral,didUpdateValueFor characteristic: CBCharacteristic,error: Error?) {guard let data = characteristic.value, error == nil else { return }if characteristic.uuid == CBUUID(string: "2A37") { // Heart Rate Measurementlet bpm = parseHeartRate(from: data)print("当前心率:\(bpm) BPM")}
}private func parseHeartRate(from data: Data) -> Int {// Flag & 8 bit/16 bit 解析细节略return Int(data[1])
}func writeExample() {let command = Data([0x01, 0xFF])targetPeripheral?.writeValue(command,for: writeCharacteristic,type: .withResponse)
}
- withResponse 确保写入成功回执,可提升可靠性。
- 解析 GATT 规范文档,才能正确理解字节含义。
9. 重连、后台与省电策略
| 场景 | 建议做法 |
|---|---|
| App 进入后台 | 开启 bluetooth-central 模式;必要时使用 scanForPeripherals:allowDuplicates = true 以便操作系统在后台唤醒。 |
| 断线重连 | 在 didDisconnectPeripheral 里调用 connect(_:options:);或使用 CBCentralManager 的 state restoration,系统会在 App 重启时恢复连接。 |
| 省电 | 扫描完即停止 (stopScan);连接成功后关闭通知只读必须通道;避免每秒轮询写。 |
10. 常见错误排查清单
| 症状 / 日志 | 可能原因 | 解决方案 |
|---|---|---|
CBError.invalidState | central.state ≠ .poweredOn | 等待 centralManagerDidUpdateState |
| 连接后立刻断开 | 配对失败 / 密钥失效 | 让用户删除系统配对记录,再尝试 |
didDiscoverServices 不回调 | 外设只广播,不允许连接 | 与厂商确认固件配置 |
特征写入返回 CBATTError.insufficientAuthentication | 需要加密连接 | 确保外设开启 MITM 保护并正确配对 |
11. 结语
通过以上步骤,你已经完整走通了 扫描 → 连接 → 发现服务 → 读写特征 的 CoreBluetooth 主流程。掌握这些要点,便能快速与各类手环、传感器或智能硬件进行稳定通信。后续你可以:
- 深入研究 GATT Profile,理解行业标准特征数据;
- 实现 RSSI 滤波 + Proximity Trigger,优化用户体验;
- 结合 Combine / AsyncStream,让异步回调更加「Swifty」。
Happy Coding & Stay Connected 📶
