You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS BLE多外设开发:扫描时可同时连接外设吗?

iOS BLE多外设连接与大尺寸数据传输问题解答

需求回顾

  • 外设需向中心设备发送最大1024字节的数据
  • 中心设备展示已连接外设列表,支持点击列表项向外设回复信息

核心疑问

中心设备能否在扫描外设的同时发起连接?这是否符合BLE规范?


一、BLE规范与iOS实现的支持情况

BLE规范允许中心设备在扫描过程中连接外设,iOS的CBCentralManager也支持这种操作,但要注意两个关键点:

  • 扫描和连接可以并行执行,不会互相中断,除非系统资源紧张
  • iOS对同时连接的外设数量有上限(通常5-7个,具体取决于设备型号和系统版本),超出上限后新的连接请求会直接失败

二、现有代码的问题与优化建议

你当前的中心端代码在didDiscover回调里直接发起连接,会导致发现一个外设就连一个,很容易快速耗尽系统连接资源。建议调整逻辑:

  1. 先收集再连接
    • 扫描阶段只把发现的外设存入数组(注意通过identifier去重),同步更新UI列表
    • 由用户手动触发连接(比如点击列表项),或者按顺序自动批量连接,避免同时发起大量连接请求
  2. 完善连接状态管理
    • 给每个外设维护连接状态(未连接/连接中/已连接),防止重复发起连接
    • 在didDisconnectPeripheral回调里及时更新列表状态

优化后的中心端关键代码:

// 存储发现的外设集合,避免重复
private var discoveredPeripherals: [CBPeripheral] = []

func viewDidLoad() {
    centralManager.scanForPeripherals(
        withServices: [TransferService.serviceUUID],
        options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
    )
}

public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
    // 通过唯一ID判断是否已添加
    if !discoveredPeripherals.contains(where: { $0.identifier == peripheral.identifier }) {
        discoveredPeripherals.append(peripheral)
        // 通知更新UI列表
        delegate?.didDiscoverPeripheral(peripheral)
    }
}

// 用户点击列表项时调用此方法发起连接
func connectToPeripheral(_ peripheral: CBPeripheral) {
    guard peripheral.state != .connected else { return }
    centralManager.connect(peripheral, options: nil)
}

三、大尺寸数据传输的关键细节

你提到广播MTU只有31字节,必须通过连接传输1024字节数据,这里补充两个核心优化点:

  1. MTU协商
    • BLE默认连接MTU是23字节(含协议头),实际可用负载仅20字节,传输1024字节需要大量分包
    • 可以调用peripheral.requestMTU(_:)协商更大的MTU(iOS最大支持185字节),大幅减少分包次数
  2. 分包传输逻辑
    • 外设端需要把1024字节数据拆分成多个数据包发送,还要处理系统的发送缓冲区限制(通过peripheralManagerIsReadyToUpdateSubscribers回调继续发送)
    • 中心端要负责接收所有数据包并拼接成完整数据

外设端分包发送示例代码:

private let maxPayloadSize = 185 // 协商后MTU减去协议头的可用长度
private var dataToSend: Data = dataFromPeripheralThatIsLargerThan1024Bytes
private var currentSendOffset = 0

public func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
    // 订阅开始后启动分包发送
    sendNextPacket()
}

private func sendNextPacket() {
    guard currentSendOffset < dataToSend.count else {
        // 所有数据发送完成
        return
    }
    let endOffset = min(currentSendOffset + maxPayloadSize, dataToSend.count)
    let packet = dataToSend.subdata(in: currentSendOffset..<endOffset)
    
    // 尝试发送数据包
    let sendSuccess = peripheralManager.updateValue(packet, for: peripheralSendingCharacteristic, onSubscribedCentrals: nil)
    if sendSuccess {
        currentSendOffset = endOffset
        sendNextPacket()
    }
    // 发送失败时,等待系统回调后继续
}

// 系统缓冲区可用时的回调
public func peripheralManagerIsReadyToUpdateSubscribers(_ peripheral: CBPeripheralManager) {
    sendNextPacket()
}

四、替代方案(当同时连接数量受限)

如果遇到系统连接数量上限的问题,可以采用以下两种方案:

  • 轮询连接:中心设备先扫描并记录所有外设,然后逐个连接、接收数据、断开,再处理下一个外设
  • 用户选择连接:中心设备只展示扫描到的外设列表,由用户选择需要连接的外设,避免同时连接过多设备

内容的提问来源于stack exchange,提问作者RobotX

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 21:05:18