
core-bluetooth
热门使用 Core Bluetooth 构建直接的蓝牙低功耗工作流。适用于实现 BLE 中心或外围 GATT 通信、使用 CBCentralManager 扫描或连接、发现服务和特征、使用 CBPeripheral 读取/写入/订阅、使用 CBPeripheralManager 发布本地服务、处理蓝牙授权、后台 BLE 模式、状态恢复、写入流控制或基于 CBUUID 的工作流。对于隐私保护的配件设置/选择流程,请先使用 accessorysetupkit,然后返回此处进行设置后的 GATT 通信。
使用 Core Bluetooth 构建直接的蓝牙低功耗工作流。适用于实现 BLE 中心或外围 GATT 通信、使用 CBCentralManager 扫描或连接、发现服务和特征、使用 CBPeripheral 读取/写入/订阅、使用 CBPeripheralManager 发布本地服务、处理蓝牙授权、后台 BLE 模式、状态恢复、写入流控制或基于 CBUUID 的工作流。对于隐私保护的配件设置/选择流程,请先使用 accessorysetupkit,然后返回此处进行设置后的 GATT 通信。
Core Bluetooth
扫描、连接并与蓝牙低功耗(BLE)设备交换数据。
涵盖中心角色(扫描和连接外围设备)、外围角色(广播服务)、后台模式和状态恢复。
使用 accessorysetupkit 进行隐私保护的配件发现和设置;
使用本技能进行直接的 Core Bluetooth GATT 通信。
目录
设置
Info.plist 键
| 键 | 用途 |
|---|---|
NSBluetoothAlwaysUsageDescription |
必需。解释应用为何使用蓝牙 |
UIBackgroundModes 包含 bluetooth-central |
后台扫描和连接 |
UIBackgroundModes 包含 bluetooth-peripheral |
后台广播 |
蓝牙授权
Core Bluetooth 没有显式的权限请求 API。添加
NSBluetoothAlwaysUsageDescription,在应用准备好使用蓝牙时创建管理器,然后检查 manager.authorization 和 manager.state。
将 .denied 和 .restricted 视为终端状态,直到用户在设置中更改;
等待 .poweredOn 后再进行扫描、连接、广播或发布服务。
中心角色:扫描
创建中心管理器
始终等待 poweredOn 状态后再扫描。
import CoreBluetooth
final class BluetoothManager: NSObject, CBCentralManagerDelegate {
private var centralManager: CBCentralManager!
private var discoveredPeripheral: CBPeripheral?
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
guard central.state == .poweredOn else { return }
startScanning()
}
}
扫描外围设备
扫描特定的服务 UUID 以节省电量。传递 nil 可发现所有外围设备(不推荐在生产环境中使用)。
let heartRateServiceUUID = CBUUID(string: "180D")
func startScanning() {
centralManager.scanForPeripherals(
withServices: [heartRateServiceUUID],
options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
)
}
func centralManager(
_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String: Any],
rssi RSSI: NSNumber
) {
guard RSSI.intValue > -70 else { return } // 过滤弱信号
// 重要:保留外围设备——否则它将被释放
discoveredPeripheral = peripheral
centralManager.stopScan()
centralManager.connect(peripheral, options: nil)
}
中心角色:连接
func centralManager(
_ central: CBCentralManager,
didConnect peripheral: CBPeripheral
) {
peripheral.delegate = self
peripheral.discoverServices([heartRateServiceUUID])
}
func centralManager(
_ central: CBCentralManager,
didDisconnectPeripheral peripheral: CBPeripheral,
timestamp: CFAbsoluteTime,
isReconnecting: Bool,
error: Error?
) {
if isReconnecting {
// 系统正在自动重新连接
return
}
// 处理断开连接——可选地重新连接
discoveredPeripheral = nil
}
发现服务和特征
实现 CBPeripheralDelegate 以遍历服务/特征树。
extension BluetoothManager: CBPeripheralDelegate {
func peripheral(
_ peripheral: CBPeripheral,
didDiscoverServices error: Error?
) {
guard let services = peripheral.services else { return }
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
}
}
func peripheral(
_ peripheral: CBPeripheral,
didDiscoverCharacteristicsFor service: CBService,
error: Error?
) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
if characteristic.properties.contains(.notify) {
peripheral.setNotifyValue(true, for: characteristic)
}
if characteristic.properties.contains(.read) {
peripheral.readValue(for: characteristic)
}
}
}
}
读取、写入和通知
读取值
func peripheral(
_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?
) {
guard let data = characteristic.value else { return }
switch characteristic.uuid {
case CBUUID(string: "2A37"):
if let heartRate = parseHeartRate(data) {
print("心率:\(heartRate) bpm")
}
case CBUUID(string: "2A19"):
let batteryLevel = data.first.map { Int($0) } ?? 0
print("电池:\(batteryLevel)%")
default:
break
}
}
private func parseHeartRate(_ data: Data) -> Int? {
guard data.count >= 2 else { return nil }
let flags = data[0]
let is16Bit = (flags & 0x01) != 0
if is16Bit {
guard data.count >= 3 else { return nil }
return Int(data[1]) | (Int(data[2]) << 8)
} else {
return Int(data[1])
}
}
写入值
func writeValue(_ data: Data, to characteristic: CBCharacteristic,
on peripheral: CBPeripheral,
preferResponse: Bool = true) {
let type: CBCharacteristicWriteType
if preferResponse, characteristic.properties.contains(.write) {
type = .withResponse
} else if characteristic.properties.contains(.writeWithoutResponse),
peripheral.canSendWriteWithoutResponse {
type = .withoutResponse
} else if characteristic.properties.contains(.write) {
type = .withResponse
} else {
return
}
guard data.count <= peripheral.maximumWriteValueLength(for: type) else { return }
peripheral.writeValue(data, for: characteristic, type: type)
}
// .withResponse 写入的确认回调。
func peripheral(
_ peripheral: CBPeripheral,
didWriteValueFor characteristic: CBCharacteristic,
error: Error?
) {
if let error {
print("写入失败:\(error.localizedDescription)")
}
}
// 在此处恢复排队的 .withoutResponse 写入。
func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {}
订阅通知
// 订阅
peripheral.setNotifyValue(true, for: characteristic)
// 取消订阅
peripheral.setNotifyValue(false, for: characteristic)
// 确认
func peripheral(
_ peripheral: CBPeripheral,
didUpdateNotificationStateFor characteristic: CBCharacteristic,
error: Error?
) {
if characteristic.isNotifying {
print("现在正在接收 \(characteristic.uuid) 的通知")
}
}
外围角色:广播
使用 CBPeripheralManager 从本地设备发布服务。
final class BLEPeripheralManager: NSObject, CBPeripheralManagerDelegate {
private var peripheralManager: CBPeripheralManager!
private let serviceUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABC")
private let charUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABD")
override init() {
super.init()
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
guard peripheral.state == .poweredOn else { return }
setupService()
}
private func setupService() {
let characteristic = CBMutableCharacteristic(
type: charUUID,
properties: [.read, .notify],
value: nil,
permissions: [.readable]
)
let service = CBMutableService(type: serviceUUID, primary: true)
service.characteristics = [characteristic]
peripheralManager.add(service)
}
func peripheralManager(
_ peripheral: CBPeripheralManager,
didAdd service: CBService,
error: Error?
) {
guard error == nil else { return }
peripheralManager.startAdvertising([
CBAdvertisementDataServiceUUIDsKey: [serviceUUID],
CBAdvertisementDataLocalNameKey: "MyDevice"
])
}
}
后台 BLE
后台中心模式
将 bluetooth-central 添加到 UIBackgroundModes。在后台:
- 扫描必须指定一个或多个服务 UUID;
nil扫描仅限前台 - 扫描选项(包括
CBCentralManagerScanOptionAllowDuplicatesKey)无效
后台外围模式
将 bluetooth-peripheral 添加到 UIBackgroundModes。在后台:
- 没有此模式,暂停时已发布的服务内容将被禁用
- 本地名称不会被广播
- 服务 UUID 移至溢出区域,需要显式服务扫描
状态恢复
状态恢复允许系统在应用因 BLE 事件被终止并重新启动后,重新创建您的中心或外围管理器。
中心管理器状态恢复
// 1. 使用恢复标识符创建
centralManager = CBCentralManager(
delegate: self,
queue: nil,
options: [CBCentralManagerOptionRestoreIdentifierKey: "myCentral"]
)
// 2. 实现恢复委托方法
func centralManager(
_ central: CBCentralManager,
willRestoreState dict: [String: Any]
) {
if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey]
as? [CBPeripheral] {
for peripheral in peripherals {
// 重新分配委托并保留
peripheral.delegate = self
discoveredPeripheral = peripheral
}
}
let restoredServices = dict[CBCentralManagerRestoredStateScanServicesKey]
as? [CBUUID]
let restoredOptions = dict[CBCentralManagerRestoredStateScanOptionsKey]
as? [String: Any]
// 如果仍然需要,使用 restoredServices/restoredOptions 恢复扫描。
}
外围管理器状态恢复
peripheralManager = CBPeripheralManager(
delegate: self,
queue: nil,
options: [CBPeripheralManagerOptionRestoreIdentifierKey: "myPeripheral"]
)
func peripheralManager(
_ peripheral: CBPeripheralManager,
willRestoreState dict: [String: Any]
) {
let services = dict[CBPeripheralManagerRestoredStateServicesKey]
as? [CBMutableService]
let advertisement = dict[CBPeripheralManagerRestoredStateAdvertisementDataKey]
as? [String: Any]
// 根据需要将应用状态连接到恢复的服务/广播。
}
常见错误
| 错误 | 修复 |
|---|---|
在 .poweredOn 之前扫描/连接 |
从 centralManagerDidUpdateState 开始 BLE 工作。 |
| 发现的外围设备未被保留 | 在连接和发现期间持有强引用。 |
生产环境扫描传递 nil 服务 |
按功能所需的服务 UUID 进行过滤。 |
在 didConnect 之前开始服务发现 |
仅从委托回调推进,并处理失败/断开连接路径。 |
| 写入忽略特征属性或有效负载限制 | 选择支持的写入类型,遵守 maximumWriteValueLength,并根据 canSendWriteWithoutResponse 控制 .withoutResponse。 |
审查清单
- [ ] 在 Info.plist 中添加了
NSBluetoothAlwaysUsageDescription - [ ] 所有 BLE 操作在
centralManagerDidUpdateState返回.poweredOn后才执行 - [ ] 发现的外围设备通过强引用保留
- [ ] 生产环境中扫描使用特定的服务 UUID(非
nil) - [ ] 在调用
discoverServices之前设置了CBPeripheralDelegate - [ ] 在读取/写入/通知之前检查了特征属性
- [ ] 写入有效负载保持在
maximumWriteValueLength(for:)范围内 - [ ]
.withoutResponse写入遵循canSendWriteWithoutResponse - [ ] 如果需要,添加了后台模式(
bluetooth-central或bluetooth-peripheral) - [ ] 如果应用需要支持通过 BLE 事件重新启动,则设置了状态恢复标识符
- [ ] 使用状态恢复时实现了
willRestoreState委托方法 - [ ] 发现目标外围设备后停止扫描
- [ ] 处理了断开连接,并带有可选的自动重新连接逻辑
- [ ] 写入类型与特征属性匹配(
.withResponse与.withoutResponse)
参考
- 扩展模式(重新连接策略、数据解析、SwiftUI 集成):references/ble-patterns.md
- Core Bluetooth 框架
- CBCentralManager
- CBPeripheral
- CBPeripheralManager
- CBService
- CBCharacteristic
- CBUUID
- CBCentralManagerDelegate
- CBPeripheralDelegate
- NSBluetoothAlwaysUsageDescription
- CBManagerAuthorization
- scanForPeripherals(withServices:options:)
- startAdvertising(_:)
- writeValue(_:for:type:)
- maximumWriteValueLength(for:)
- canSendWriteWithoutResponse
- 配置后台执行模式





