即使在.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])
}
}
相关代码是
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
实例是可以使用的,因为它是作为参数传递的。