iOS CoreBluetooth 多链路管理
CoreBluetooth 是 iOS 中用于蓝牙低功耗(BLE)开发的核心框架,多链路管理指同时连接、管理多个 BLE 外设设备,是工业、智能家居等场景的常见需求。下面从核心思路、完整实现、关键优化三个维度讲解。
一、核心设计思路
多链路管理的核心挑战是:
- 避免并发连接导致的系统资源竞争
- 统一管理多个外设的连接状态、数据交互
- 处理断线重连、连接超时等异常场景
设计原则:
- 使用队列管理连接请求,避免同时发起多个连接
- 用数据模型封装每个外设的信息(UUID、状态、连接参数)
- 监听系统蓝牙状态,统一处理蓝牙开关、权限等全局事件
- 对每个外设的操作(连接、读写、断开)做独立的异常处理
二、完整实现代码
1. 先定义核心数据模型
封装外设信息,便于统一管理:
import CoreBluetooth
// 外设连接状态
enum BLEPeripheralState {
case idle // 空闲(未连接)
case connecting // 连接中
case connected // 已连接
case disconnecting // 断开中
case failed // 连接失败
}
// 外设模型
class BLEPeripheralModel {
let peripheral: CBPeripheral // 外设对象
let identifier: UUID // 外设唯一标识
var state: BLEPeripheralState = .idle // 连接状态
var connectTimeout: TimeInterval = 10 // 连接超时时间
var retryCount: Int = 0 // 重连次数
let maxRetryCount: Int = 3 // 最大重连次数
// 数据交互相关
var discoveredServices: [CBService]?
var discoveredCharacteristics: [CBCharacteristic]?
init(peripheral: CBPeripheral) {
self.peripheral = peripheral
self.identifier = peripheral.identifier
}
}
2. 多链路管理核心类
实现外设扫描、连接、数据交互、状态管理的完整逻辑:
import CoreBluetooth
import UIKit
class BLECentralManager: NSObject {
// MARK: - 核心属性
private let centralManager: CBCentralManager
private let bleQueue = DispatchQueue(label: "com.ble.central.queue", qos: .utility) // 蓝牙操作队列
private let connectQueue = DispatchQueue(label: "com.ble.connect.queue") // 连接请求队列
// 已发现/已管理的外设列表
private var peripheralModels: [UUID: BLEPeripheralModel] = [:]
// 连接超时定时器
private var connectTimers: [UUID: Timer] = [:]
// 单例(全局唯一的蓝牙管理器)
static let shared = BLECentralManager()
// 状态回调(供外部监听)
var onPeripheralStateChanged: ((UUID, BLEPeripheralState) -> Void)?
var onDataReceived: ((UUID, Data) -> Void)?
// MARK: - 初始化
private override init() {
centralManager = CBCentralManager(delegate: nil, queue: bleQueue)
super.init()
centralManager.delegate = self
}
// MARK: - 对外API
/// 开始扫描外设
/// - Parameter serviceUUIDs: 要扫描的服务UUID(nil表示扫描所有)
func startScanning(serviceUUIDs: [CBUUID]? = nil) {
guard centralManager.state == .poweredOn else {
print("蓝牙未开启,无法扫描")
return
}
// 避免重复扫描
if centralManager.isScanning { return }
// 设置扫描参数:不重复发现、扫描20秒后停止
centralManager.scanForPeripherals(withServices: serviceUUIDs, options: [
CBCentralManagerScanOptionAllowDuplicatesKey: false
])
// 自动停止扫描(防止耗电)
bleQueue.asyncAfter(deadline: .now() + 20) { [weak self] in
self?.stopScanning()
}
}
/// 停止扫描
func stopScanning() {
guard centralManager.isScanning else { return }
centralManager.stopScan()
}
/// 连接指定外设
/// - Parameter peripheralID: 外设唯一标识
func connectPeripheral(peripheralID: UUID) {
guard let model = peripheralModels[peripheralID],
centralManager.state == .poweredOn else {
print("外设不存在或蓝牙未开启")
return
}
// 避免重复连接
if model.state == .connecting || model.state == .connected { return }
// 加入连接队列,串行执行
connectQueue.async { [weak self] in
guard let self = self else { return }
// 更新状态
model.state = .connecting
self.onPeripheralStateChanged?(model.identifier, .connecting)
// 发起连接
self.centralManager.connect(model.peripheral, options: [
CBCentralManagerConnectOptionNotifyOnConnectionKey: true,
CBCentralManagerConnectOptionNotifyOnDisconnectionKey: true
])
// 设置连接超时
self.setupConnectTimeoutTimer(for: model.identifier)
}
}
/// 断开指定外设连接
/// - Parameter peripheralID: 外设唯一标识
func disconnectPeripheral(peripheralID: UUID) {
guard let model = peripheralModels[peripheralID],
model.state == .connected else { return }
model.state = .disconnecting
onPeripheralStateChanged?(model.identifier, .disconnecting)
centralManager.cancelPeripheralConnection(model.peripheral)
// 清除超时定时器
connectTimers[peripheralID]?.invalidate()
connectTimers.removeValue(forKey: peripheralID)
}
/// 断开所有外设连接
func disconnectAllPeripherals() {
peripheralModels.forEach { disconnectPeripheral(peripheralID: $0.key) }
}
/// 读取外设特征值
/// - Parameters:
/// - peripheralID: 外设ID
/// - characteristicUUID: 特征UUID
func readCharacteristic(peripheralID: UUID, characteristicUUID: CBUUID) {
guard let model = peripheralModels[peripheralID],
model.state == .connected,
let characteristics = model.discoveredCharacteristics else {
print("外设未连接或特征未发现")
return
}
guard let char = characteristics.first(where: { $0.uuid == characteristicUUID }) else {
print("特征不存在")
return
}
model.peripheral.readValue(for: char)
}
/// 向外设写入数据
/// - Parameters:
/// - peripheralID: 外设ID
/// - characteristicUUID: 特征UUID
/// - data: 要写入的数据
func writeData(peripheralID: UUID, characteristicUUID: CBUUID, data: Data) {
guard let model = peripheralModels[peripheralID],
model.state == .connected,
let characteristics = model.discoveredCharacteristics else {
print("外设未连接或特征未发现")
return
}
guard let char = characteristics.first(where: { $0.uuid == characteristicUUID }) else {
print("特征不存在")
return
}
// 写入方式:WithResponse(需要响应)/ WithoutResponse(无需响应)
model.peripheral.writeValue(data, for: char, type: .withResponse)
}
// MARK: - 私有方法
/// 设置连接超时定时器
/// - Parameter peripheralID: 外设ID
private func setupConnectTimeoutTimer(for peripheralID: UUID) {
guard let model = peripheralModels[peripheralID] else { return }
// 先清除旧定时器
connectTimers[peripheralID]?.invalidate()
// 创建新定时器
let timer = Timer.scheduledTimer(withTimeInterval: model.connectTimeout, repeats: false) { [weak self] timer in
guard let self = self else { return }
// 超时处理:断开连接、更新状态
self.disconnectPeripheral(peripheralID: peripheralID)
model.state = .failed
self.onPeripheralStateChanged?(peripheralID, .failed)
// 重连逻辑(如果未达到最大重连次数)
if model.retryCount < model.maxRetryCount {
model.retryCount += 1
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.connectPeripheral(peripheralID: peripheralID)
}
}
timer.invalidate()
self.connectTimers.removeValue(forKey: peripheralID)
}
// 加入runloop(否则后台队列的定时器不生效)
RunLoop.current.add(timer, forMode: .common)
connectTimers[peripheralID] = timer
}
}
// MARK: - CBCentralManagerDelegate
extension BLECentralManager: CBCentralManagerDelegate {
/// 蓝牙状态变化
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOff:
print("蓝牙已关闭")
disconnectAllPeripherals()
peripheralModels.forEach {
$0.value.state = .idle
onPeripheralStateChanged?($0.key, .idle)
}
case .poweredOn:
print("蓝牙已开启")
case .unauthorized:
print("蓝牙权限未授权")
case .unsupported:
print("设备不支持蓝牙")
default:
break
}
}
/// 发现外设
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// 避免重复添加
if peripheralModels[peripheral.identifier] == nil {
let model = BLEPeripheralModel(peripheral: peripheral)
peripheralModels[model.identifier] = model
print("发现外设:(peripheral.name ?? "未知名称"),ID:(model.identifier)")
}
}
/// 外设连接成功
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
guard let model = peripheralModels[peripheral.identifier] else { return }
// 清除超时定时器
connectTimers[model.identifier]?.invalidate()
connectTimers.removeValue(forKey: model.identifier)
// 更新状态
model.state = .connected
model.retryCount = 0 // 重置重连次数
onPeripheralStateChanged?(model.identifier, .connected)
// 设置外设代理,发现服务和特征
peripheral.delegate = self
peripheral.discoverServices(nil) // nil表示发现所有服务
}
/// 外设连接失败
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
guard let model = peripheralModels[peripheral.identifier] else { return }
// 清除超时定时器
connectTimers[model.identifier]?.invalidate()
connectTimers.removeValue(forKey: model.identifier)
// 更新状态
model.state = .failed
onPeripheralStateChanged?(model.identifier, .failed)
print("连接外设失败:(error?.localizedDescription ?? "未知错误"),ID:(model.identifier)")
// 重连逻辑
if model.retryCount < model.maxRetryCount {
model.retryCount += 1
connectQueue.asyncAfter(deadline: .now() + 1) { [weak self] in
self?.connectPeripheral(peripheralID: model.identifier)
}
}
}
/// 外设断开连接
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
guard let model = peripheralModels[peripheral.identifier] else { return }
// 更新状态
model.state = .idle
onPeripheralStateChanged?(model.identifier, .idle)
print("外设断开连接:(error?.localizedDescription ?? "正常断开"),ID:(model.identifier)")
// 可选:自动重连(根据业务需求)
// connectPeripheral(peripheralID: model.identifier)
}
}
// MARK: - CBPeripheralDelegate
extension BLECentralManager: CBPeripheralDelegate {
/// 发现外设服务
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let model = peripheralModels[peripheral.identifier], error == nil else {
print("发现服务失败:(error?.localizedDescription ?? "未知错误")")
return
}
model.discoveredServices = peripheral.services
print("发现服务:(peripheral.services?.count ?? 0) 个,外设ID:(model.identifier)")
// 发现每个服务下的特征
peripheral.services?.forEach { service in
peripheral.discoverCharacteristics(nil, for: service)
}
}
/// 发现服务特征
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let model = peripheralModels[peripheral.identifier], error == nil else {
print("发现特征失败:(error?.localizedDescription ?? "未知错误")")
return
}
if model.discoveredCharacteristics == nil {
model.discoveredCharacteristics = []
}
model.discoveredCharacteristics?.append(contentsOf: service.characteristics ?? [])
print("发现特征:(service.characteristics?.count ?? 0) 个,外设ID:(model.identifier)")
// 可选:订阅特征通知(实时接收数据)
service.characteristics?.forEach { char in
if char.properties.contains(.notify) {
peripheral.setNotifyValue(true, for: char)
}
}
}
/// 特征值更新(通知/读取)
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
guard let model = peripheralModels[peripheral.identifier],
error == nil,
let data = characteristic.value else {
print("特征值更新失败:(error?.localizedDescription ?? "无数据")")
return
}
// 回调给外部处理数据
onDataReceived?(model.identifier, data)
print("收到数据:(data),外设ID:(model.identifier),特征UUID:(characteristic.uuid)")
}
/// 写入数据响应
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
guard let model = peripheralModels[peripheral.identifier] else { return }
if let error = error {
print("写入数据失败:(error.localizedDescription),外设ID:(model.identifier)")
} else {
print("写入数据成功,外设ID:(model.identifier),特征UUID:(characteristic.uuid)")
}
}
}
3. 使用示例
在 ViewController 中调用管理类:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 初始化蓝牙管理器
let bleManager = BLECentralManager.shared
// 监听外设状态变化
bleManager.onPeripheralStateChanged = { peripheralID, state in
DispatchQueue.main.async {
print("外设 (peripheralID) 状态变化:(state)")
// 更新UI:如连接状态、按钮可点击性等
}
}
// 监听数据接收
bleManager.onDataReceived = { peripheralID, data in
DispatchQueue.main.async {
print("收到外设 (peripheralID) 数据:(data)")
// 解析数据并更新UI
}
}
// 开始扫描外设
bleManager.startScanning()
// 模拟:扫描5秒后连接第一个发现的外设
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
if let firstPeripheralID = bleManager.peripheralModels.keys.first {
bleManager.connectPeripheral(peripheralID: firstPeripheralID)
}
}
}
// 断开连接按钮点击事件
@IBAction func disconnectButtonTapped(_ sender: UIButton) {
if let firstPeripheralID = BLECentralManager.shared.peripheralModels.keys.first {
BLECentralManager.shared.disconnectPeripheral(peripheralID: firstPeripheralID)
}
}
}
三、关键优化点
1. 性能优化
- 串行队列管理连接:使用
connectQueue串行处理连接请求,避免同时发起多个连接导致系统资源耗尽 - 合理设置扫描参数:避免
CBCentralManagerScanOptionAllowDuplicatesKey = true(重复发现),减少系统开销 - 自动停止扫描:扫描一段时间后主动停止,降低耗电
- 批量操作:多个外设的读写操作可分批次执行,避免并发读写导致的数据包丢失
2. 稳定性优化
- 连接超时处理:每个连接请求设置超时定时器,避免无限等待
- 断线重连策略:可配置最大重连次数,重连间隔逐步增加(如1s→3s→5s)
- 状态校验:所有操作前校验蓝牙状态、外设状态,避免无效操作
- 异常捕获:对读写、连接等操作的错误进行统一处理
3. 资源管理
- 及时释放资源:断开连接后清除定时器、清空特征缓存
- 限制最大连接数:iOS 系统对 BLE 同时连接数有上限(约7-8个),需在代码中限制,避免超出系统能力
- 内存管理:使用
weak self避免循环引用,防止内存泄漏
四、注意事项
-
权限配置:在
Info.plist中添加蓝牙权限描述:NSBluetoothAlwaysUsageDescription(iOS 13+)NSBluetoothPeripheralUsageDescription(iOS 12及以下)
-
后台模式:如需后台运行蓝牙,需开启
Info.plist中的bluetooth-central后台模式 -
系统限制:
- iOS 设备同时连接的 BLE 外设数量建议不超过6个
- 蓝牙操作必须在后台队列执行,避免阻塞主线程
-
测试建议:在真实设备上测试(模拟器不支持 CoreBluetooth)
总结
- 多链路管理核心是队列化连接请求 + 状态统一管理 + 异常容错处理,通过单例类封装所有蓝牙操作,对外提供简洁API。
- 关键优化点包括:串行处理连接、设置连接超时、断线重连、限制最大连接数,避免系统资源耗尽。
- 必须处理蓝牙状态变化、权限、后台模式等系统级问题,确保多链路管理的稳定性和可靠性。