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

问题描述 投票:1回答:1

即使在.poweredOn的stateUpdateHandler中访问CBCentralManager,CBCentralManager也会解包为nil。 如果我在访问CBCentralManager之前放一个sleep(1),就没有问题了。 为什么会发生这种情况? 如果是.poweredOn,那里肯定已经有一个非nil实例了?

在我的操作系统升级到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])
    }

}
swift core-bluetooth ios-bluetooth
1个回答
2
投票

相关代码是

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

和:

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

的调用。CBCentralManager 初始化器开始动作。你必须假设委托人将立即被初始化器调用,也就是说,在初始化器返回之前,在结果被分配到实例变量之前,第二段代码就已经运行了 centralManager.

当调用初始化器时,如果蓝牙设备已经开机,这可能总是会发生。如果它还没有开机,委托人将在稍后被调用。

总之,你不应该担心这个问题。取而代之的是

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

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

只需使用:

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

在委托人中 CBCentralManager 实例是可以使用的,因为它是作为参数传递的。

© www.soinside.com 2019 - 2024. All rights reserved.