CoreBluetooth CBCentralManager 解包为 nil,即使从 stateUpdateHandler 的 case .poweredOn 解包。知道为什么吗?

CoreBluetooth CBCentralManager unwraps as nil even when unwrapped from the stateUpdateHandler's case .poweredOn. Any idea why?

CBCentralManager 展开为 nil,即使在 .poweredOn 上的 stateUpdateHandler 中访问也是如此。如果我在访问 CBCentralManager 之前放置一个 sleep(1),就没有问题。为什么会这样?如果它是 .poweredOn 肯定已经有一个非 nil 实例了吗?

以下代码一直有效,直到将我的 OS 升级到 Catalina 并将 Xcode 升级到 11.5。

extension BLEController: CBCentralManagerDelegate {

    func centralManagerDidUpdateState(_ central: CBCentralManager)  {
        switch central.state {
        case .unknown:
            print("central.state is .unknown")
        case .resetting:
            print("central.state is .resetting")
        case .unsupported:
            print("central.state is .unsupported")
        case .unauthorized:
            print("central.state is .unauthorized")
        case .poweredOff:
            print("central.state is .poweredOff")
        case .poweredOn:
            print("Bluetooth module is on.  Searching...")

            sleep(1)  // works fine if this is here, but if .poweredOn it should be non-nil?

            // unwraps as nil
            guard let cManager = centralManager else {
                print("centralManager is nil")
                return
            }

            cManager.scanForPeripherals(withServices: [self.heartRateServiceCBUUID])

        @unknown default:
            return
        }
    }
}

完整代码:

import Foundation
import CoreBluetooth





class BLEController: CBCentralManager {

    var btQueue = DispatchQueue(label: "BT Queue")

    var bpmReceived: ((Int) -> Void)?

    var bpm: Int? {
        didSet {
            self.bpmReceived?(self.bpm!)
        }
    }


    var centralManager: CBCentralManager!
    var heartRatePeripheral: CBPeripheral!

    let heartRateServiceCBUUID = CBUUID(string: "0x180D")

    let heartRateMeasurementCharacteristicCBUUID = CBUUID(string: "2A37")
    let batteryLevelCharacteristicCBUUID = CBUUID(string: "2A19")



    func start() -> Void {
        print("bluetooth started")
        self.centralManager = CBCentralManager(delegate: self, queue: self.btQueue)
    }


    func stop() -> Void {
        centralManager.cancelPeripheralConnection(heartRatePeripheral)
    }


    func centralManager(_ central: CBCentralManager,
                        didDiscover peripheral: CBPeripheral,
                        advertisementData: [String: Any],
                        rssi RSSI: NSNumber) {

        heartRatePeripheral = peripheral
        heartRatePeripheral.delegate = self
        centralManager.stopScan()
        centralManager.connect(heartRatePeripheral)
    }



    func centralManager(_ central: CBCentralManager,
                        didConnect peripheral: CBPeripheral) {
        print("Connected to HRM!")
        heartRatePeripheral.discoverServices(nil)
    }


    func onHeartRateReceived(_ heartRate: Int) {
        self.bpm = heartRate
    }
}



extension BLEController: CBCentralManagerDelegate {

    func centralManagerDidUpdateState(_ central: CBCentralManager)  {
        switch central.state {
        case .unknown:
            print("central.state is .unknown")
        case .resetting:
            print("central.state is .resetting")
        case .unsupported:
            print("central.state is .unsupported")
        case .unauthorized:
            print("central.state is .unauthorized")
        case .poweredOff:
            print("central.state is .poweredOff")
        case .poweredOn:
            print("Bluetooth module is on.  Searching...")

            sleep(1)  // works fine if this is here, but if .poweredOn it should be non-nil?

            // unwraps as nil
            guard let cManager = centralManager else {
                print("centralManager is nil")
                return
            }

            cManager.scanForPeripherals(withServices: [self.heartRateServiceCBUUID])

        @unknown default:
            return
        }
    }
}



extension BLEController: CBPeripheralDelegate {
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        guard let services = peripheral.services else { return }

        for service in services {
            peripheral.discoverCharacteristics(nil, for: service)
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard let characteristics = service.characteristics else { return }

        for characteristic in characteristics {
            if characteristic.properties.contains(.read) {
                peripheral.readValue(for: characteristic)
            }
            if characteristic.properties.contains(.notify) {
                peripheral.setNotifyValue(true, for: characteristic)
            }
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
                    error: Error?) {
        switch characteristic.uuid {
        case batteryLevelCharacteristicCBUUID:
            let percent = batteryLevel(from: characteristic)
            print("Battery level: \(percent)%")
        case heartRateMeasurementCharacteristicCBUUID:
            let bpm = heartRate(from: characteristic)
            onHeartRateReceived(bpm)
        default:
            return
        }
    }

    private func heartRate(from characteristic: CBCharacteristic) -> Int {
        guard let characteristicData = characteristic.value else { return -1 }
        let byteArray = [UInt8](characteristicData)

        let firstBitValue = byteArray[0] & 0x01
        if firstBitValue == 0 {
            // Heart Rate Value Format is in the 2nd byte
            return Int(byteArray[1])
        } else {
            // Heart Rate Value Format is in the 2nd and 3rd bytes
            return (Int(byteArray[1]) << 8) + Int(byteArray[2])
        }
    }


    private func batteryLevel(from characteristic: CBCharacteristic) -> Int {
        guard let characteristicData = characteristic.value else { return -1 }
        let byteArray = [UInt8](characteristicData)
        return Int(byteArray[0])
    }

}

相关代码为:

 self.centralManager = CBCentralManager(delegate: self, queue: self.btQueue)

和:

func centralManagerDidUpdateState(_ central: CBCentralManager)  {
    switch central.state {
        ...
    case .poweredOn:
        ...
}

CBCentralManager 初始化程序的调用启动了操作。您必须假设委托将立即从初始化程序中调用,即第二段代码是 运行 在初始化程序返回之前以及在结果被分配给实例变量之前 centralManager.

如果在调用初始化程序时蓝牙设备已经开机,这可能总是会发生。如果还没有开机,稍后会调用delegate。

无论如何,你应该不必担心。而不是:

guard let cManager = centralManager else {
    print("centralManager is nil")
    return
}

cManager.scanForPeripherals(withServices: [self.heartRateServiceCBUUID])

只需使用:

central.scanForPeripherals(withServices: [self.heartRateServiceCBUUID])

在委托中,CBCentralManager 实例可用,因为它作为参数传递。