Core Bluetooth角色定义与切换基础逻辑
Core Bluetooth框架将蓝牙通信的参与方分为两类核心角色:中央设备(Central)和外设(Peripheral)。外设角色的核心职责是广播自身的服务与特征信息,等待中央设备的连接请求;中央设备则负责扫描周围的外设广播,发起连接并与之进行数据交互。在iOS设备间实现聊天功能时,两台设备既可能需要作为外设广播自身可被连接的信号,也可能需要作为中央设备主动扫描并连接对方,因此角色切换是必须实现的基础能力。
角色切换的实现首先需要明确设备的当前状态,iOS系统不允许同一时间同一设备同时以中央和外设身份运行核心任务,因此需要通过状态管理来协调两个角色的启停。通常的做法是定义一个全局的状态枚举,标记设备当前是处于外设广播状态、中央扫描状态,还是已连接通信状态。当设备需要切换角色时,先停止当前角色的核心任务,再初始化另一个角色的相关实例并启动对应流程。比如设备原本作为外设正在广播,当需要主动连接其他设备时,需要先停止广播,再初始化CBCentralManager实例开始扫描。
需要注意的是,iOS系统的蓝牙后台模式会影响角色切换的行为。如果应用需要支持后台场景下的角色切换,需要在Info.plist中配置对应的后台模式,比如bluetooth-central和bluetooth-peripheral,否则应用在退到后台后,中央设备的扫描和外设的广播都会被系统暂停,导致角色无法正常切换。另外,角色切换过程中要避免频繁启停蓝牙管理器,频繁创建和销毁CBCentralManager、CBPeripheralManager实例会增加系统资源消耗,还可能引发状态异常,建议在整个应用生命周期内维护两个管理器的单例,通过控制其启停状态来实现角色切换。
中央设备与外设的通信链路搭建
搭建通信链路的第一步是外设端的广播配置。外设需要创建CBPeripheralManager实例,然后定义聊天功能对应的服务(CBService)和特征(CBCharacteristic)。服务需要设置一个唯一的UUID,特征也需要独立的UUID,同时要根据需求设置特征的属性,比如支持写入、读取、通知等。配置完成后,外设调用startAdvertising方法开始广播,广播数据中需要包含服务的UUID,这样中央设备才能扫描到对应的外设。
中央设备端需要创建CBCentralManager实例,实现其代理方法。当中央设备扫描到符合服务UUID的外设时,会触发didDiscover peripheral的代理回调,此时可以停止扫描,调用connect方法发起连接。连接成功后,中央设备会触发didConnect peripheral回调,接着需要调用peripheral.discoverServices方法发现外设的服务,再发现服务对应的特征。对于聊天功能来说,通常需要两个特征:一个用于接收外设发送的消息(开启通知属性),一个用于向外部发送消息(支持写入属性)。
以下是外设端配置服务和广播的基础代码示例:
import CoreBluetooth
class PeripheralManager: NSObject, CBPeripheralManagerDelegate {
private var peripheralManager: CBPeripheralManager!
// 聊天服务的UUID,可自定义生成
private let chatServiceUUID = CBUUID(string: "12345678-1234-1234-1234-1234567890AB")
// 接收消息特征的UUID,支持写入
private let receiveCharacteristicUUID = CBUUID(string: "12345678-1234-1234-1234-1234567890AC")
// 发送消息特征的UUID,支持通知
private let sendCharacteristicUUID = CBUUID(string: "12345678-1234-1234-1234-1234567890AD")
private var sendCharacteristic: CBMutableCharacteristic?
override init() {
super.init()
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state == .poweredOn {
// 蓝牙开启,配置服务和特征
let service = CBMutableService(type: chatServiceUUID, primary: true)
let receiveChar = CBMutableCharacteristic(type: receiveCharacteristicUUID, properties: [.write, .writeWithoutResponse], value: nil, permissions: [.writeable])
sendCharacteristic = CBMutableCharacteristic(type: sendCharacteristicUUID, properties: [.notify], value: nil, permissions: [.readable])
service.characteristics = [receiveChar, sendCharacteristic!]
peripheralManager.add(service)
}
}
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
if error == nil {
// 服务添加成功,开始广播
peripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey: [chatServiceUUID]])
}
}
// 接收中央设备写入的消息
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
for request in requests {
if let value = request.value, let message = String(data: value, encoding: .utf8) {
print("收到消息:\(message)")
}
peripheralManager.respond(to: request, withResult: .success)
}
}
// 向中央设备发送消息
func sendMessage(_ message: String) {
guard let data = message.data(using: .utf8), let characteristic = sendCharacteristic else { return }
peripheralManager.updateValue(data, for: characteristic, onSubscribedCentrals: nil)
}
}
中央设备端的连接和特征发现代码如下:
import CoreBluetooth
class CentralManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
private var centralManager: CBCentralManager!
private var connectedPeripheral: CBPeripheral?
private var receiveCharacteristic: CBCharacteristic?
private var sendCharacteristic: CBCharacteristic?
private let chatServiceUUID = CBUUID(string: "12345678-1234-1234-1234-1234567890AB")
private let receiveCharacteristicUUID = CBUUID(string: "12345678-1234-1234-1234-1234567890AC")
private let sendCharacteristicUUID = CBUUID(string: "12345678-1234-1234-1234-1234567890AD")
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
// 开始扫描对应服务的外设
centralManager.scanForPeripherals(withServices: [chatServiceUUID], options: nil)
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// 发现目标外设,停止扫描并连接
connectedPeripheral = peripheral
connectedPeripheral?.delegate = self
centralManager.stopScan()
centralManager.connect(peripheral, options: nil)
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
// 连接成功,发现服务
peripheral.discoverServices([chatServiceUUID])
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
if service.uuid == chatServiceUUID {
// 发现聊天服务,查找特征
peripheral.discoverCharacteristics([receiveCharacteristicUUID, sendCharacteristicUUID], for: service)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
if characteristic.uuid == receiveCharacteristicUUID {
receiveCharacteristic = characteristic
// 订阅该特征,接收外设的通知消息
peripheral.setNotifyValue(true, for: characteristic)
} else if characteristic.uuid == sendCharacteristicUUID {
sendCharacteristic = characteristic
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.uuid == receiveCharacteristicUUID, let data = characteristic.value, let message = String(data: data, encoding: .utf8) {
print("收到外设消息:\(message)")
}
}
// 向外设发送消息
func sendMessage(_ message: String) {
guard let data = message.data(using: .utf8), let peripheral = connectedPeripheral, let characteristic = sendCharacteristic else { return }
peripheral.writeValue(data, for: characteristic, type: .withResponse)
}
}

数据传输稳定性优化策略
蓝牙传输的稳定性首先受限于MTU(最大传输单元)的限制。Core Bluetooth的默认MTU通常较小,单次传输的数据量如果超过MTU,系统会自动分包,但分包过程可能出现丢包或者顺序错乱的问题。因此需要在应用层做数据分包和重组的逻辑,发送端将长消息按照MTU大小拆分,每个分包添加序号和总包数标记,接收端根据序号重组完整消息,同时校验总包数是否匹配,避免消息不完整。
丢包重传机制也是提升稳定性的关键。当发送端调用写入方法时,如果是带响应的写入(type为.withResponse),可以通过didWriteValueFor的回调判断是否写入成功,如果失败则触发重传。如果是无响应写入或者通知类型的发送,无法收到对方的接收确认,此时可以自定义应用层协议,接收端收到消息后返回一个确认包,发送端在设定时间内没有收到确认包则重新发送该消息。同时要避免短时间内发送大量数据,蓝牙的带宽有限,高频发送会导致数据拥堵,建议添加发送队列,控制每秒的发送数据量,或者等待上一条消息的确认后再发送下一条。
连接状态的维护也不容忽视。蓝牙连接可能因为距离、干扰等原因断开,需要在断开连接的代理回调中做重连逻辑。中央设备端实现centralManager(_:didDisconnectPeripheral:error:)回调,外设端实现peripheralManager(_:didUnsubscribeFrom:for:)或者连接断开的相关回调,当检测到断开后,先清理当前连接状态,再根据当前角色重新启动扫描或者广播,尝试重新建立连接。另外,在数据传输过程中可以添加心跳机制,定期发送小数据包检测连接是否存活,如果多次心跳没有收到响应,主动断开连接并触发重连,避免无效的数据发送。
还需要注意iOS系统的蓝牙权限和后台限制。从iOS 13开始,使用蓝牙功能需要申请蓝牙权限,用户拒绝权限后无法使用相关功能,需要在应用启动时引导用户开启权限。如果应用需要后台传输数据,除了配置后台模式,还需要合理处理系统对后台任务的资源限制,避免在后台时蓝牙任务被系统挂起。另外,不同iOS版本的Core Bluetooth实现可能存在细微差异,测试时需要覆盖主流的iOS版本,确保兼容性。
Core BluetoothiOS蓝牙聊天蓝牙数据传输修改时间:2026-08-26 08:26:03