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

如何让蓝牙通信方法返回didUpdateValue回调的响应结果

解决蓝牙异步回调转同步返回的问题

因为CoreBluetooth的通信是异步回调模式(发送指令后通过didUpdateValue代理方法接收响应),要实现var batteryLevel = getBatteryLevel()这种同步写法,需要把异步回调转换为同步返回逻辑,下面提供两种实用方案:


方案一:使用 Async/Await(iOS 15+ 推荐)

利用Swift的异步等待特性,将代理回调包装成异步函数,写法更简洁且不会阻塞线程。

步骤1:定义指令枚举与等待逻辑

先定义一个枚举标记不同指令(用于区分响应归属),再用withCheckedThrowingContinuation包装等待响应的逻辑:

// 自定义指令枚举,对应不同的蓝牙命令
enum BluetoothCommand: CaseIterable {
    case getVersion
    case getBatteryLevel
    
    // 转成对应指令的Data
    var commandData: Data {
        switch self {
        case .getVersion: return Data([0x01])
        case .getBatteryLevel: return Data([0x02])
        }
    }
    
    // 从响应Data解析对应的指令类型
    static func fromResponse(_ data: Data) -> BluetoothCommand? {
        guard let firstByte = data.first else { return nil }
        switch firstByte {
        case 0x81: return .getVersion
        case 0x82: return .getBatteryLevel
        default: return nil
        }
    }
}

class BluetoothManager: NSObject, CBPeripheralDelegate {
    private var connectedDevice: CBPeripheral?
    private var rxCharacteristic: CBCharacteristic?
    // 保存等待中的异步续体,关联指令类型
    private var pendingContinuations: [BluetoothCommand: CheckedContinuation<Data, Error>] = [:]
    
    // 封装等待响应的异步方法
    private func waitForResponse(for command: BluetoothCommand) async throws -> Data {
        return try await withCheckedThrowingContinuation { continuation in
            pendingContinuations[command] = continuation
            // 发送指令
            connectedDevice?.writeValue(command.commandData, for: rxCharacteristic!, type: .withResponse)
        }
    }

步骤2:代理方法中触发异步续体

在didUpdateValue中解析响应,找到对应的续体并返回结果:

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        guard let responseData = characteristic.value, error == nil else {
            // 所有等待中的请求抛出错误
            pendingContinuations.values.forEach { $0.resume(throwing: error ?? NSError(domain: "BluetoothError", code: -1, userInfo: [NSLocalizedDescriptionKey: "响应错误"])) }
            pendingContinuations.removeAll()
            return
        }
        
        guard let command = BluetoothCommand.fromResponse(responseData) else {
            pendingContinuations.values.forEach { $0.resume(throwing: NSError(domain: "BluetoothError", code: -2, userInfo: [NSLocalizedDescriptionKey: "无效响应"])) }
            pendingContinuations.removeAll()
            return
        }
        
        // 触发对应指令的续体,返回响应数据
        if let continuation = pendingContinuations.removeValue(forKey: command) {
            continuation.resume(returning: responseData)
        }
    }

步骤3:实现同步风格的业务方法

现在可以直接写异步方法,调用时用await即可:

// 获取电池电量的异步方法
    func getBatteryLevel() async throws -> Int {
        let responseData = try await waitForResponse(for: .getBatteryLevel)
        // 解析响应数据(示例:取第一个字节作为电量百分比)
        guard let batteryByte = responseData.last else {
            throw NSError(domain: "BluetoothError", code: -3, userInfo: [NSLocalizedDescriptionKey: "解析失败"])
        }
        return Int(batteryByte)
    }
}

调用示例

Task {
    do {
        let batteryLevel = try await bluetoothManager.getBatteryLevel()
        print("当前电池电量:\(batteryLevel)%")
    } catch {
        print("获取电量失败:\(error.localizedDescription)")
    }
}

方案二:使用 DispatchSemaphore(兼容 iOS 15 之前版本)

通过信号量阻塞当前线程,等待代理回调完成后释放信号量,实现同步返回。注意:不能在主线程调用,避免阻塞UI。

步骤1:定义线程安全的等待变量

class BluetoothManager: NSObject, CBPeripheralDelegate {
    private var connectedDevice: CBPeripheral?
    private var rxCharacteristic: CBCharacteristic?
    private let queue = DispatchQueue(label: "com.bluetooth.queue") // 线程安全队列
    private var pendingSemaphores: [BluetoothCommand: DispatchSemaphore] = [:]
    private var latestResponse: Data?
    private var latestError: Error?

步骤2:实现同步业务方法

func getBatteryLevel() throws -> Int {
        let command = BluetoothCommand.getBatteryLevel
        let semaphore = DispatchSemaphore(value: 0)
        
        // 线程安全保存信号量
        queue.sync {
            pendingSemaphores[command] = semaphore
        }
        
        // 发送指令
        connectedDevice?.writeValue(command.commandData, for: rxCharacteristic!, type: .withResponse)
        
        // 等待信号量,设置5秒超时
        let timeoutResult = semaphore.wait(timeout: .now() + 5)
        guard timeoutResult == .success else {
            queue.sync { pendingSemaphores.removeValue(forKey: command) }
            throw NSError(domain: "BluetoothError", code: -1, userInfo: [NSLocalizedDescriptionKey: "请求超时"])
        }
        
        // 线程安全读取结果
        return try queue.sync {
            if let error = latestError {
                self.latestError = nil
                throw error
            }
            
            guard let responseData = latestResponse else {
                throw NSError(domain: "BluetoothError", code: -2, userInfo: [NSLocalizedDescriptionKey: "无响应数据"])
            }
            self.latestResponse = nil
            
            guard let batteryByte = responseData.last else {
                throw NSError(domain: "BluetoothError", code: -3, userInfo: [NSLocalizedDescriptionKey: "解析失败"])
            }
            return Int(batteryByte)
        }
    }

步骤3:代理方法中释放信号量

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        queue.sync {
            guard let responseData = characteristic.value, error == nil else {
                latestError = error ?? NSError(domain: "BluetoothError", code: -1, userInfo: [NSLocalizedDescriptionKey: "响应错误"])
                pendingSemaphores.values.forEach { $0.signal() }
                pendingSemaphores.removeAll()
                return
            }
            
            guard let command = BluetoothCommand.fromResponse(responseData) else {
                latestError = NSError(domain: "BluetoothError", code: -2, userInfo: [NSLocalizedDescriptionKey: "无效响应"])
                pendingSemaphores.values.forEach { $0.signal() }
                pendingSemaphores.removeAll()
                return
            }
            
            latestResponse = responseData
            latestError = nil
            
            if let semaphore = pendingSemaphores.removeValue(forKey: command) {
                semaphore.signal()
            }
        }
    }
}

调用示例

DispatchQueue.global().async {
    do {
        let batteryLevel = try bluetoothManager.getBatteryLevel()
        DispatchQueue.main.async {
            print("当前电池电量:\(batteryLevel)%")
        }
    } catch {
        DispatchQueue.main.async {
            print("获取电量失败:\(error.localizedDescription)")
        }
    }
}

关键注意事项

  1. 指令与响应的绑定:必须通过指令枚举区分不同请求的响应,避免多请求时结果混乱。
  2. 超时处理:两种方案都要设置超时,防止无限等待。
  3. 线程安全:信号量方案必须用串行队列保护共享变量,避免多线程冲突。
  4. UI线程保护:信号量方案绝对不能在主线程调用,Async/Await方案可以在主线程的Task中调用,但耗时操作仍会被系统调度到后台。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 17:47:47