一、基础概念
1. 蓝牙有几种技术标准?
- 经典蓝牙 (Bluetooth Classic): 传输速率高、功耗大,主要用于音频传输、文件传输等场景。iOS 对经典蓝牙的设备间通信支持有限(主要通过 MFI 认证)。
- 低功耗蓝牙 (Bluetooth Low Energy, BLE): 功耗低、数据量小,主要用于设备状态同步、传感器数据采集等。iOS 蓝牙开发主要围绕 BLE。
2. iOS 中处理蓝牙的核心框架是哪个?
- CoreBluetooth.framework。
- 它提供了与 BLE 设备交互的所有必要类和方法。
3. 解释 BLE 中的中心设备 (Central) 和外设 (Peripheral) 角色
- 外设 (Peripheral): 广播数据的设备,例如智能手环、心率监测器。它持有数据。
- 中心设备 (Central): 扫描并连接外设的设备,例如 iPhone。它消费数据。
- 在 iOS 开发中,我们的 App 通常作为 Central 去连接其他 BLE 设备。 但 iOS 设备也可以作为 Peripheral(通过
CBPeripheralManager)。
二、核心流程与 API
4. 描述一个完整的 Central 端连接和数据交互流程
-
创建中心管理器
centralManager = CBCentralManager(delegate: self, queue: nil) -
监听蓝牙状态 (CBCentralManagerDelegate)
func centralManagerDidUpdateState(_ central: CBCentralManager) { if central.state == .poweredOn { // 蓝牙已开启,开始扫描 central.scanForPeripherals(withServices: nil, options: nil) } } -
发现外设
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { // 检查外设名称或广播数据,找到目标设备 if peripheral.name == "MyDevice" { self.targetPeripheral = peripheral central.stopScan() central.connect(peripheral, options: nil) } } -
连接外设
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { // 连接成功,设置 Peripheral 的代理并开始发现服务 peripheral.delegate = self peripheral.discoverServices(nil) // 传入 nil 发现所有服务 } -
发现服务 (CBPeripheralDelegate)
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { if let services = peripheral.services { for service in services { // 根据目标服务的 UUID 进行过滤 if service.uuid == CBUUID(string: "180D") { // 心率服务 peripheral.discoverCharacteristics(nil, for: service) } } } } -
发现特征
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { if let characteristics = service.characteristics { for characteristic in characteristics { let properties = characteristic.properties // 1. 通知类 if properties.contains(.notify) { peripheral.setNotifyValue(true, for: characteristic) print("启用通知: \(characteristic.uuid)") } if properties.contains(.indicate) { peripheral.setNotifyValue(true, for: characteristic) print("启用指示: \(characteristic.uuid)") } // 2. 读取类 if properties.contains(.read) { peripheral.readValue(for: characteristic) print("读取特征: \(characteristic.uuid)") } // 3. 写入类 if properties.contains(.write) { // 需要响应的写入 print("支持写入(带响应): \(characteristic.uuid)") } if properties.contains(.writeWithoutResponse) { // 无响应的写入,速度更快但不可靠 print("支持无响应写入: \(characteristic.uuid)") } // 4. 其他属性 if properties.contains(.broadcast) { print("支持广播: \(characteristic.uuid)") } if properties.contains(.extendedProperties) { print("支持扩展属性: \(characteristic.uuid)") } if properties.contains(.authenticatedSignedWrites) { print("支持认证签名写入: \(characteristic.uuid)") } if properties.contains(.notifyEncryptionRequired) { print("需要加密的通知: \(characteristic.uuid)") } if properties.contains(.indicateEncryptionRequired) { print("需要加密的指示: \(characteristic.uuid)") } } } -
数据交互
- 读取数据:
peripheral.readValue(for: characteristic)然后在didUpdateValueFor回调中获取数据。 - 订阅通知:
peripheral.setNotifyValue(true, for: characteristic)数据更新时,会在didUpdateValueFor回调中收到。 - 写入数据:
peripheral.writeValue(data, for: characteristic, type: .withResponse)(或.withoutResponse)
- 读取数据:
-
断开连接
centralManager.cancelPeripheralConnection(peripheral)
5. 解释 BLE 的 GATT 结构
- GATT (Generic Attribute Profile): 定义了 BLE 设备间数据传输的格式和结构。
- 层级关系: Device -> Service -> Characteristic -> Descriptor
- Service (服务): 一个设备提供的特定功能,例如电池服务、设备信息服务。用 UUID 标识。
- Characteristic (特征): 服务下的具体数据点,是数据交互的真正载体。例如电池电量、心率测量值。它有自己的 UUID、值和属性(读、写、通知等)。
- Descriptor (描述符): 对 Characteristic 的额外描述,例如用户描述、特征格式等。
6. CBCharacteristic 的 properties 有哪些?各自含义是什么?
.read: 可读.write: 可写(需要响应).writeWithoutResponse: 可写(不需要响应,速度快但不可靠).notify: 可订阅(外设主动推送数据,无确认).indicate: 可指示(外设主动推送数据,有确认,更可靠)- 等等。在操作一个特征前,必须检查其属性。
三、进阶问题
7. 后台模式下的蓝牙处理
- 配置: 在
Capabilities中开启Background Modes并勾选Uses Bluetooth LE accessories。 - 限制:
- 扫描时需要使用 Service UUIDs(
scanForPeripherals(withServices: [CBUUID], options: ...)),否则在后台扫描不到设备。 - App 在后台或被挂起时,所有蓝牙事件都会在后台队列中被缓存,当 App 回到前台时会一并交付。
- 扫描时需要使用 Service UUIDs(
- 状态保存与恢复: 通过实现
restoreIdentifier和centralManager(_:willRestoreState:)方法,可以让系统在 App 被杀死后重新启动时恢复蓝牙连接和状态。
8. 如何保证蓝牙连接的稳定性和重连机制?
- 监听断开事件: 实现
centralManager(_:didDisconnectPeripheral:error:)代理方法。 - 实现重连逻辑: 在断开回调中,根据错误码和业务逻辑进行重试连接。
- 连接超时处理:
CoreBluetooth没有原生超时,需要自己用DispatchWorkItem或Timer实现。 - 错误处理: 妥善处理常见的错误,如连接失败、服务发现失败等。
除了上述方法,还有更多提升蓝牙连接成功率的方案、失败原因分析和压测方法:
1. 连接参数优化
// 使用带选项的连接
let options: [String: Any] = [
CBConnectPeripheralOptionNotifyOnConnectionKey: true,
CBConnectPeripheralOptionNotifyOnDisconnectionKey: true,
CBConnectPeripheralOptionNotifyOnNotificationKey: true,
CBConnectPeripheralOptionEnableTransportBridgingKey: true,
CBConnectPeripheralOptionRequiresANCS: false
]
centralManager.connect(peripheral, options: options)
这些是 Core Bluetooth 中连接外设时的选项参数,用于控制连接行为和系统通知。
各选项参数详解
1. CBConnectPeripheralOptionNotifyOnConnectionKey
CBConnectPeripheralOptionNotifyOnConnectionKey: true
作用:当外设连接成功时,即使应用在后台或挂起状态,系统也会显示通知提醒用户。
使用场景:
let options: [String: Any] = [
CBConnectPeripheralOptionNotifyOnConnectionKey: true
]
centralManager.connect(peripheral, options: options)
// 当连接成功时,即使用户没有打开你的App
// 系统会在通知中心显示:"[App名称] 已连接到 [设备名称]"
2. CBConnectPeripheralOptionNotifyOnDisconnectionKey
CBConnectPeripheralOptionNotifyOnDisconnectionKey: true
作用:当外设断开连接时(无论是主动断开还是意外断开),系统会显示通知。
使用场景:
let options: [String: Any] = [
CBConnectPeripheralOptionNotifyOnDisconnectionKey: true
]
// 当设备断开时,系统显示:
// "[App名称] 与 [设备名称] 的连接已断开"
// 这对于需要用户知道连接状态的医疗设备、安全设备等很重要
3. CBConnectPeripheralOptionNotifyOnNotificationKey
CBConnectPeripheralOptionNotifyOnNotificationKey: true
作用:当外设发送通知(特征值变化)而应用在后台时,系统会唤醒应用并允许处理数据。
使用场景:
let options: [String: Any] = [
CBConnectPeripheralOptionNotifyOnNotificationKey: true
]
// 应用在后台时,如果设备发送了特征值更新
// 系统会短暂唤醒应用(有几秒时间处理数据)
// 适合需要实时响应设备数据的场景
4. CBConnectPeripheralOptionEnableTransportBridgingKey
CBConnectPeripheralOptionEnableTransportBridgingKey: true
作用:在支持多设备协同的苹果生态中(如 iPhone + Apple Watch),允许连接在不同设备间桥接。
使用场景:
let options: [String: Any] = [
CBConnectPeripheralOptionEnableTransportBridgingKey: true
]
// 当用户有多个苹果设备时,连接信息可以在设备间共享
// 例如:在iPhone上配对的设备,在Apple Watch上也能识别
// 需要设备支持蓝牙4.0以上
5. CBConnectPeripheralOptionRequiresANCS
CBConnectPeripheralOptionRequiresANCS: false
作用:指定连接是否要求 ANCS(Apple Notification Center Service)。
使用场景:
let options: [String: Any] = [
CBConnectPeripheralOptionRequiresANCS: false
]
// ANCS 是苹果通知中心服务,允许蓝牙设备显示iPhone通知
// 设为 false 表示不强制要求设备支持ANCS
// 通常用于非通知类设备(传感器、控制器等)
实际应用示例
医疗设备连接配置
func connectMedicalDevice(_ peripheral: CBPeripheral) {
let options: [String: Any] = [
CBConnectPeripheralOptionNotifyOnConnectionKey: true,
CBConnectPeripheralOptionNotifyOnDisconnectionKey: true,
CBConnectPeripheralOptionNotifyOnNotificationKey: true,
CBConnectPeripheralOptionEnableTransportBridgingKey: false, // 医疗设备通常不需要跨设备
CBConnectPeripheralOptionRequiresANCS: false
]
centralManager.connect(peripheral, options: options)
}
智能家居设备配置
func connectHomeDevice(_ peripheral: CBPeripheral) {
let options: [String: Any] = [
CBConnectPeripheralOptionNotifyOnConnectionKey: false, // 家居设备连接不需要通知
CBConnectPeripheralOptionNotifyOnDisconnectionKey: true, // 断开需要知道
CBConnectPeripheralOptionNotifyOnNotificationKey: true, // 需要接收状态更新
CBConnectPeripheralOptionEnableTransportBridgingKey: true, // 支持多设备控制
CBConnectPeripheralOptionRequiresANCS: false
]
centralManager.connect(peripheral, options: options)
}
音频设备配置
func connectAudioDevice(_ peripheral: CBPeripheral) {
let options: [String: Any] = [
CBConnectPeripheralOptionNotifyOnConnectionKey: true, // 连接成功提示
CBConnectPeripheralOptionNotifyOnDisconnectionKey: true, // 断开提示
CBConnectPeripheralOptionNotifyOnNotificationKey: false,
CBConnectPeripheralOptionEnableTransportBridgingKey: true, // 支持跨设备音频
CBConnectPeripheralOptionRequiresANCS: true // 音频设备通常需要显示通知
]
centralManager.connect(peripheral, options: options)
}
注意事项
- 后台模式:使用这些选项通常需要在 Capabilities 中开启 Bluetooth LE 后台模式
- 用户隐私:系统通知会显示App名称,要考虑用户体验
- 电量影响:
NotifyOnNotificationKey会频繁唤醒App,影响电量 - 审核要求:如果滥用这些选项可能导致App Store审核被拒
这些选项主要用于提升用户体验和设备连接的可靠性,根据具体业务场景选择合适的配置。
2. 扫描策略优化
func startOptimizedScan() {
let scanOptions: [String: Any] = [
CBCentralManagerScanOptionAllowDuplicatesKey: false,
CBCentralManagerScanOptionSolicitedServiceUUIDsKey: [yourServiceUUID]
]
centralManager.scanForPeripherals(withServices: [yourServiceUUID], options: scanOptions)
// 扫描超时控制
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
self.centralManager.stopScan()
}
}
旨在提高扫描效率并减少资源消耗。
方法整体作用
这是一个优化的蓝牙设备扫描函数,通过限制扫描条件和添加超时控制,实现精准、高效的设备发现。
1. 扫描选项配置
let scanOptions: [String: Any] = [
CBCentralManagerScanOptionAllowDuplicatesKey: false,
CBCentralManagerScanOptionSolicitedServiceUUIDsKey: [yourServiceUUID]
]
CBCentralManagerScanOptionAllowDuplicatesKey: false
作用:禁止重复报告同一设备
true:每次收到设备广播都回调,会频繁收到同一设备false:每个设备只回调一次(除非丢失后重新发现)- 优势:减少不必要的回调,节省电量和CPU资源
CBCentralManagerScanOptionSolicitedServiceUUIDsKey: [yourServiceUUID]
作用:只扫描包含指定服务的设备
- 只发现广播中包含
yourServiceUUID服务的设备 - 过滤掉不相关的蓝牙设备
- 优势:精准扫描,减少干扰设备
2. 开始扫描
centralManager.scanForPeripherals(withServices: [yourServiceUUID], options: scanOptions)
作用:启动蓝牙扫描,双重过滤机制
withServices: [yourServiceUUID]:系统层过滤,只发现目标服务设备scanOptions:应用层进一步控制扫描行为
3. 超时控制
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
self.centralManager.stopScan()
}
作用:10秒后自动停止扫描
- 防止无限扫描消耗电量
- 避免用户等待时间过长
- 10秒是经验值,可根据业务调整
优化前后的对比
优化前(基础扫描)
// 问题:会发现所有蓝牙设备,耗电且效率低
func startBasicScan() {
centralManager.scanForPeripherals(withServices: nil, options: nil)
// 没有超时控制,需要手动调用 stopScan
}
优化后(精准扫描)
// 优势:只找目标设备,自动停止,高效省电
func startOptimizedScan() {
let scanOptions: [String: Any] = [
CBCentralManagerScanOptionAllowDuplicatesKey: false, // 去重
CBCentralManagerScanOptionSolicitedServiceUUIDsKey: [yourServiceUUID] // 过滤
]
centralManager.scanForPeripherals(withServices: [yourServiceUUID], options: scanOptions)
// 自动超时停止
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
self.centralManager.stopScan()
}
}
实际应用场景
场景1:智能家居设备配网
class SmartHomeManager {
let iotServiceUUID = CBUUID(string: "6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
func scanForSmartDevices() {
let scanOptions: [String: Any] = [
CBCentralManagerScanOptionAllowDuplicatesKey: false,
CBCentralManagerScanOptionSolicitedServiceUUIDsKey: [iotServiceUUID]
]
centralManager.scanForPeripherals(withServices: [iotServiceUUID], options: scanOptions)
// 智能家居设备通常快速响应,8秒足够
DispatchQueue.main.asyncAfter(deadline: .now() + 8) {
self.centralManager.stopScan()
self.handleScanTimeout()
}
}
}
场景2:医疗设备扫描
class MedicalDeviceManager {
let healthServiceUUID = CBUUID(string: "180A") // 设备信息服务
func scanForMedicalDevices() {
let scanOptions: [String: Any] = [
CBCentralManagerScanOptionAllowDuplicatesKey: false, // 医疗设备不需要重复数据
CBCentralManagerScanOptionSolicitedServiceUUIDsKey: [healthServiceUUID]
]
centralManager.scanForPeripherals(withServices: [healthServiceUUID], options: scanOptions)
// 医疗设备可能需要更长时间
DispatchQueue.main.asyncAfter(deadline: .now() + 15) {
self.centralManager.stopScan()
}
}
}
场景3:多服务设备扫描
class MultiServiceScanner {
let primaryServiceUUID = CBUUID(string: "12345678-1234-1234-1234-123456789012")
let secondaryServiceUUID = CBUUID(string: "12345678-1234-1234-1234-123456789013")
func scanForMultiServiceDevices() {
let targetServices = [primaryServiceUUID, secondaryServiceUUID]
let scanOptions: [String: Any] = [
CBCentralManagerScanOptionAllowDuplicatesKey: false,
CBCentralManagerScanOptionSolicitedServiceUUIDsKey: targetServices
]
centralManager.scanForPeripherals(withServices: targetServices, options: scanOptions)
DispatchQueue.main.asyncAfter(deadline: .now() + 12) {
self.centralManager.stopScan()
}
}
}
- 省电:不扫描无关设备,自动停止
- 高效:快速找到目标设备,减少用户等待
- 精准:双重过滤机制,只发现相关设备
- 稳定:避免重复回调,减少内存占用
注意事项
func startOptimizedScan() {
// 先停止之前的扫描,避免多重扫描
centralManager.stopScan()
let scanOptions: [String: Any] = [
CBCentralManagerScanOptionAllowDuplicatesKey: false,
CBCentralManagerScanOptionSolicitedServiceUUIDsKey: [yourServiceUUID]
]
centralManager.scanForPeripherals(withServices: [yourServiceUUID], options: scanOptions)
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
self.centralManager.stopScan()
// 可以在这里添加扫描完成后的逻辑
self.onScanComplete()
}
}
3. 连接状态管理
class BluetoothManager: NSObject {
private var connectionTimer: DispatchWorkItem?
private var retryCount = 0
private let maxRetryCount = 3
private var connectionStartTime: Date?
func connectWithStrategy(_ peripheral: CBPeripheral) {
guard retryCount < maxRetryCount else {
print("超过最大重试次数")
return
}
connectionStartTime = Date()
setupConnectionTimeout()
// 检查系统蓝牙状态
guard centralManager.state == .poweredOn else {
handleBluetoothStateIssue()
return
}
// 避免重复连接
if peripheral.state != .connected && peripheral.state != .connecting {
centralManager.connect(peripheral, options: options)
}
}
private func setupConnectionTimeout() {
connectionTimer?.cancel()
connectionTimer = DispatchWorkItem { [weak self] in
self?.handleConnectionTimeout()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 15, execute: connectionTimer!)
}
}
4. 设备缓存策略
// 使用 identifier 重连已知设备
func reconnectToKnownPeripherals() {
let knownIdentifiers = retrieveKnownPeripheralIdentifiers()
let peripherals = centralManager.retrievePeripherals(withIdentifiers: knownIdentifiers)
for peripheral in peripherals {
if peripheral.state != .connected {
connectWithStrategy(peripheral)
}
}
}
这个方法通过系统缓存的重连机制,**自动重新连接用户之前成功连接过的蓝牙设备**,提升用户体验。
### 1. 获取已知设备标识
```swift
let knownIdentifiers = retrieveKnownPeripheralIdentifiers()
作用:从本地存储(如 UserDefaults、数据库)获取之前成功连接过的设备标识符数组。
可能的 retrieveKnownPeripheralIdentifiers 实现:
private func retrieveKnownPeripheralIdentifiers() -> [UUID] {
guard let savedIdentifiers = UserDefaults.standard.array(forKey: "KnownPeripheralIdentifiers") as? [String] else {
return []
}
return savedIdentifiers.compactMap { UUID(uuidString: $0) }
}
2. 从系统缓存检索外设
let peripherals = centralManager.retrievePeripherals(withIdentifiers: knownIdentifiers)
作用:Core Bluetooth 系统会尝试从缓存中找回这些标识符对应的外设对象。
重要特性:
- 只返回当前在系统蓝牙范围内的设备
- 设备必须曾经成功连接并配对过
- 系统维护一个已知设备的缓存列表
3. 遍历并连接设备
for peripheral in peripherals {
if peripheral.state != .connected {
connectWithStrategy(peripheral)
}
}
作用:检查每个设备状态,对未连接的设备启动重连流程。
状态检查的意义:
.connected:已连接,跳过.disconnected:未连接,启动重连.connecting:连接中,通常跳过避免重复.disconnecting:断开中,等待完成后重连
完整的重连系统实现
1. 设备标识管理
class BluetoothConnectionManager {
private let knownDevicesKey = "KnownPeripheralIdentifiers"
// 保存成功连接的设备
func saveConnectedPeripheral(_ peripheral: CBPeripheral) {
var knownIdentifiers = retrieveKnownPeripheralIdentifiers()
let identifierString = peripheral.identifier.uuidString
if !knownIdentifiers.contains(peripheral.identifier) {
knownIdentifiers.append(peripheral.identifier)
saveIdentifiersToStorage(knownIdentifiers)
}
}
// 移除设备
func removeKnownPeripheral(_ peripheral: CBPeripheral) {
var knownIdentifiers = retrieveKnownPeripheralIdentifiers()
knownIdentifiers.removeAll { $0 == peripheral.identifier }
saveIdentifiersToStorage(knownIdentifiers)
}
private func saveIdentifiersToStorage(_ identifiers: [UUID]) {
let identifierStrings = identifiers.map { $0.uuidString }
UserDefaults.standard.set(identifierStrings, forKey: knownDevicesKey)
}
}
2. 增强的重连策略
func reconnectToKnownPeripherals() {
let knownIdentifiers = retrieveKnownPeripheralIdentifiers()
guard !knownIdentifiers.isEmpty else {
print("没有已知设备需要重连")
return
}
let peripherals = centralManager.retrievePeripherals(withIdentifiers: knownIdentifiers)
print("找到 \(peripherals.count)/\(knownIdentifiers.count) 个已知设备在范围内")
for peripheral in peripherals {
switch peripheral.state {
case .disconnected:
print("重连设备: \(peripheral.name ?? "未知设备")")
connectWithStrategy(peripheral)
case .connected:
print("设备已连接: \(peripheral.name ?? "未知设备")")
// 可以在这里触发服务发现等后续操作
discoverServicesForConnectedPeripheral(peripheral)
case .connecting:
print("设备连接中: \(peripheral.name ?? "未知设备")")
case .disconnecting:
print("设备断开中: \(peripheral.name ?? "未知设备")")
@unknown default:
break
}
}
}
3. 智能连接策略
func connectWithStrategy(_ peripheral: CBPeripheral) {
let connectionOptions: [String: Any] = [
CBConnectPeripheralOptionNotifyOnConnectionKey: true,
CBConnectPeripheralOptionNotifyOnDisconnectionKey: true
]
// 设置连接超时
setupConnectionTimeout(for: peripheral)
// 记录连接尝试
logConnectionAttempt(for: peripheral)
centralManager.connect(peripheral, options: connectionOptions)
}
private func setupConnectionTimeout(for peripheral: CBPeripheral) {
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) { [weak self] in
if peripheral.state == .connecting {
print("连接超时,取消连接: \(peripheral.name ?? "未知设备")")
self?.centralManager.cancelPeripheralConnection(peripheral)
}
}
}
实际应用场景
场景1:App启动时自动重连
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// App启动时尝试重连已知设备
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.bluetoothManager.reconnectToKnownPeripherals()
}
return true
}
}
场景2:从后台返回时重连
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(appDidBecomeActive),
name: UIApplication.didBecomeActiveNotification,
object: nil
)
}
@objc func appDidBecomeActive() {
// 从后台返回时检查重连
bluetoothManager.reconnectToKnownPeripherals()
}
}
场景3:网络状态变化时重连
class BluetoothManager {
private func setupNetworkObserver() {
NotificationCenter.default.addObserver(
self,
selector: #selector(networkStatusChanged),
name: .networkStatusChanged, // 自定义通知
object: nil
)
}
@objc private func networkStatusChanged() {
// 网络恢复时尝试重连设备
reconnectToKnownPeripherals()
}
}
优势与价值
- 用户体验:用户无需手动重新连接设备
- 自动化:App能智能恢复之前的连接状态
- 效率:只连接在范围内的已知设备
- 稳定性:系统级缓存提供可靠的重连机制
注意事项
func reconnectToKnownPeripherals() {
// 1. 检查蓝牙状态
guard centralManager.state == .poweredOn else {
print("蓝牙未开启,无法重连")
return
}
// 2. 限制重连频率,避免过于频繁
if Date().timeIntervalSince(lastReconnectAttempt) < 30 {
return
}
lastReconnectAttempt = Date()
let knownIdentifiers = retrieveKnownPeripheralIdentifiers()
let peripherals = centralManager.retrievePeripherals(withIdentifiers: knownIdentifiers)
for peripheral in peripherals {
if peripheral.state != .connected {
connectWithStrategy(peripheral)
}
}
// 3. 记录重连日志
logReconnectionAttempt(peripherals.count)
}
5. 信号强度筛选
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral,
advertisementData: [String : Any], rssi RSSI: NSNumber) {
// 只连接信号强度足够的设备
if RSSI.intValue > -75 { // 可根据实际情况调整阈值
connectablePeripherals[peripheral.identifier] = peripheral
}
}
1. 系统层面原因
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
if let error = error as? CBError {
switch error.code {
case .unknown:
print("未知错误,可能是系统蓝牙栈问题")
case .connectionTimeout:
print("连接超时,设备可能不在范围内")
case .peerRemovedPairingInformation:
print("配对信息被清除,需要重新配对")
case .encryptionTimedOut:
print("加密超时")
case .tooManyLEPairedDevices:
print("系统蓝牙配对设备数量超限")
default:
print("其他错误: \(error)")
}
}
}
2. 设备层面原因
- 距离过远:RSSI 信号弱
- 设备忙:设备正在处理其他连接
- 电量不足:设备进入低功耗模式
- 硬件故障:设备蓝牙模块异常
3. 应用层面原因
- 资源竞争:多个线程同时操作蓝牙
- 状态管理错误:未正确管理连接状态
- 内存压力:应用内存不足导致连接被终止
压测方案
1. 自动化测试框架
class BluetoothStressTest {
private var testResults: [TestResult] = []
private var testCount = 0
private let totalTests = 100
func startStressTest() {
for i in 0..<totalTests {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) * 2) {
self.executeSingleTest(iteration: i)
}
}
}
private func executeSingleTest(iteration: Int) {
let testStartTime = Date()
let peripheral = discoverPeripheral() // 发现设备
connectWithStrategy(peripheral) { [weak self] success, duration, error in
let result = TestResult(
iteration: iteration,
success: success,
duration: duration,
error: error,
timestamp: Date()
)
self?.testResults.append(result)
// 立即断开,准备下一次测试
if success {
self?.centralManager.cancelPeripheralConnection(peripheral)
}
}
}
}
2. 性能指标监控
struct TestResult {
let iteration: Int
let success: Bool
let duration: TimeInterval
let error: Error?
let timestamp: Date
let deviceModel: String
let iOSVersion: String
let rssi: Int
}
class PerformanceMonitor {
func generateReport() {
let totalTests = testResults.count
let successCount = testResults.filter { $0.success }.count
let successRate = Double(successCount) / Double(totalTests) * 100
let durations = testResults.compactMap { $0.duration }
let avgDuration = durations.reduce(0, +) / Double(durations.count)
print("""
压力测试报告:
- 总测试次数: \(totalTests)
- 成功率: \(String(format: "%.2f", successRate))%
- 平均连接时间: \(String(format: "%.2f", avgDuration))秒
- 失败分布: \(getErrorDistribution())
""")
}
}
3. 真实场景模拟
class RealWorldSimulator {
func simulateRealUsage() {
// 模拟不同距离
simulateDistanceVariation()
// 模拟信号干扰
simulateSignalInterference()
// 模拟多设备竞争
simulateMultipleDevices()
// 模拟应用状态变化
simulateAppStateChanges()
}
private func simulateAppStateChanges() {
// 前后台切换
NotificationCenter.default.addObserver(
self,
selector: #selector(appWillResignActive),
name: UIApplication.willResignActiveNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(appDidBecomeActive),
name: UIApplication.didBecomeActiveNotification,
object: nil
)
}
}
4. 环境变量测试
func testUnderDifferentConditions() {
// 测试不同系统版本
// 测试不同设备型号
// 测试不同蓝牙版本
// 测试不同距离和障碍物情况
}
最佳实践总结
- 分级重试:根据错误类型采用不同的重试策略
- 用户引导:在连接失败时给出明确的用户指引
- 降级方案:准备备用的配网方式(如 SoftAP)
- 数据上报:收集连接失败数据用于优化
- 设备指纹:记录设备特征,优化重连策略
这些方案可以显著提升蓝牙连接的成功率和稳定性,特别是在复杂的物联网环境中。
9. 蓝牙配对和绑定 (Bonding) 的区别?
- 配对 (Pairing): 一个一次性的过程,用于交换密钥、认证身份,建立安全的连接。
- 绑定 (Bonding): 在配对成功后,将交换的密钥(LTK, Long-Term Key)存储起来,以便后续重新连接时无需再次配对。在 iOS 上,这个过程由系统自动管理。
10. 作为 Peripheral 端 (CBPeripheralManager) 需要做什么?
- 创建
CBPeripheralManager。 - 设置服务和特征(
CBMutableService和CBMutableCharacteristic)。 - 发布服务到本地数据库。
- 开始广播。
- 处理 Central 的订阅、读请求和写请求。
四、实战与经验
11. 你在项目中遇到的蓝牙难点是什么?如何解决的?
这是一个开放性问题,考察实际经验。可能的答案:
- 连接不稳定: 实现了指数退避算法的重连机制。
- 数据分包与组包: BLE 单次传输数据量有限(通常是 20 字节),需要设计协议来处理长数据。
- 多设备连接管理: 使用字典或数组管理多个
CBPeripheral实例。 - 不同厂商设备兼容性: 处理非标准的 UUID 或不符合规范的 GATT 结构。
12. 如何调试蓝牙问题?
- 使用 LightBlue、nRF Connect 等第三方 App 模拟 Peripheral 或扫描设备,验证硬件本身是否正常。
- 在 Xcode 中查看
CoreBluetooth的日志(有时需要额外的系统日志工具)。 - 检查所有的 Delegate 回调,特别是错误回调。
- 使用
Packet Logger(需要苹果开发者账号)进行底层 HCI 日志抓取,这是最强大的调试手段。
13. 了解 Bluetooth 5.0 的新特性吗?
- 2M PHY: 更高的传输速率。
- LE Audio: 新一代蓝牙音频标准。
- Long Range: 更长的传输距离。
- Advertising Extensions: 更强大的广播数据能力。
- 注意: 这些特性需要手机硬件和外围设备同时支持,并且 iOS API 可能对部分特性有版本要求。