【问题标题】:Swift CoreBluetooth: Should CentralManager run in a separate thread?Swift CoreBluetooth:CentralManager 是否应该在单独的线程中运行?
【发布时间】:2023-03-04 11:05:01
【问题描述】:

现在我在主线程中运行所有内容,到目前为止,我只注意到多次 UI 有点滞后。

我想知道 utilizint CoreBluetooth 库在并发方面的一般做法是什么?

您能否提供一些示例,如果有的话,应该在其他队列中运行什么?

我对蓝牙的使用:

我扫描两个外围设备,通过发送适当的值作为 CBPeripheralManager 来控制它们,以使它们开始从 IMU 发送数据(50Hz/100Hz 取决于值)。

我对来自标签的数据进行同步和规范化,并使用流媒体将它们写入文件。

传输完成后,我通过按钮触发相关动作手动发送数据文件。

我的代码

class BluetoothTagController: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, CBPeripheralManagerDelegate{
static let sharedInstance = BluetoothTagController()
var transferCharacteristic:CBMutableCharacteristic!
var centralManager : CBCentralManager!
var sensorTagPeripheral : CBPeripheral!
var synchronizer:DataSynchronizer!
var sensorTagPeripheralArray : [CBPeripheral] = []
var peripheralManager: CBPeripheralManager!
var bluetoothIsON:Bool = false

var tag1Updating:Bool = false
var tag2Updating:Bool = false
var tag1Changed:Bool = false
var tag2Changed:Bool = false
var tagsIds:[String] = []
var peripheralCounter:Int = 0
var peripheralCounter2:Int = 0
var writeCounter:Int = 0
var timerSet:Bool = false
var haveBeenStarted:Bool = false


override init()
{
    super.init()

    centralManager = CBCentralManager(delegate: self, queue: nil)
    peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
    self.synchronizer = DataSynchronizer(frequency: 1)



}


func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {

    print("WATCH OUT")
    //        print(service)
}
func setHaveBeenStarted( haveBeen: Bool) {
    haveBeenStarted = haveBeen
}

func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {

    print("subscription started")
    var intVal: NSInteger = 0
    if haveBeenStarted == true {
        intVal = 2
    }
    let valueData:Data = Data(buffer: UnsafeBufferPointer(start: &intVal, count: 1))



    var didSend:Bool = self.peripheralManager.updateValue(valueData, for: self.transferCharacteristic, onSubscribedCentrals: nil)
    print(didSend)

}

func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
    if peripheral.state != .poweredOn
    {
        print("no power")
        self.bluetoothIsON = false
        return
    }
    self.bluetoothIsON = true
    print("powered on")
    let serviceCBUUID = CBUUID(string: "5DC90000-8F79-462B-98D7-C1F8C766FA47")
    let transferService:CBMutableService = CBMutableService(type: serviceCBUUID, primary: true)
    let characteristicBUUID = CBUUID(string: "5DC90001-8F79-462B-98D7-C1F8C766FA47")
    var intVal: NSInteger = 2
    let valueData:Data = Data(buffer: UnsafeBufferPointer(start: &intVal, count: 1))
    let transferCharacteristic = CBMutableCharacteristic(type: characteristicBUUID, properties: .notify, value: nil, permissions: .readable)
    self.transferCharacteristic = transferCharacteristic

    transferService.characteristics = [transferCharacteristic as CBCharacteristic]
    self.peripheralManager.add(transferService)
    self.peripheralManager.startAdvertising(nil)


}


func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {

    print("didReceiveReadRequest")
    //
}

func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) {
    print("Unsubscribed")

    //        var intVal: NSInteger = 0
    //        let valueData:Data = Data(buffer: UnsafeBufferPointer(start: &intVal, count: 1))
    //        self.peripheralManager.updateValue(valueData, for: self.transferCharacteristic, onSubscribedCentrals: nil)

}


/******* CBCentralManagerDelegate *******/

// Check status of BLE hardware
func centralManagerDidUpdateState(_ central: CBCentralManager) {

    if central.state == .poweredOn {
        // Scan for peripherals if BLE is turned on
        central.scanForPeripherals(withServices: nil, options: nil)

    }
    else {
        // Can have different conditions for all states if needed - show generic alert for now

    }
}



// Check out the discovered peripherals to find Sensor Tag
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
    print("array2 contains" + "\(self.sensorTagPeripheralArray.count)")

    if SensorTag.sensorTagFound(advertisementData) == true {

        // Update Status Label'
        self.sensorTagPeripheral = peripheral
        self.sensorTagPeripheral.delegate = self
        self.centralManager.connect(peripheral, options: nil)
        if  !self.sensorTagPeripheralArray.contains(peripheral)
        {
            self.sensorTagPeripheralArray.append(peripheral)
            self.tagsIds.append("\(peripheral.identifier)")
            //                self.centralManager.connectPeripheral(peripheral, options: nil)
        }

        else {

            //showAlertWithText(header: "Warning", message: "SensorTag Not Found")
        }
    }
}

// Discover services of the peripheral
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {

    print("connected " + "\(peripheral.identifier)")
    print("array contains" + "\(self.sensorTagPeripheralArray.count)")

    numberOfTagsSending = numberOfTagsSending + 1
    peripheral.discoverServices(nil)
}


// If disconnected, start searching again
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
    //        print("error")
    //        print(error)

    //        self.sensorTagPeripheralArray.arrayRemovingObject(peripheral)
    //        print(sensorTagPeripheralArray)
    numberOfTagsSending = numberOfTagsSending - 1
    print("removed")

    synchronizer.alreadySynced = false


    central.scanForPeripherals(withServices: nil, options: nil)
}

func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
    print("ciekawe")
}

func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {

    print("looking for p services")
    print("discovered services " + "\(peripheral.identifier)")
    for service in peripheral.services! {

        let thisService = service as CBService
        if SensorTag.validService(thisService) {
            // Discover characteristics of all valid services
            peripheral.discoverCharacteristics(nil, for: thisService)
        }
    }
}


// Enable notification and sensor for each characteristic of valid service
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {

    //        print("discovered characteristic " + "\(peripheral.identifier)")



    var enableValue = 1
    let enablyBytes = Data(buffer: UnsafeBufferPointer(start: &enableValue, count: 1))
    //        print("\n")
    for charateristic in service.characteristics! {
        print(charateristic.uuid)
        let thisCharacteristic = charateristic as CBCharacteristic
        if SensorTag.validDataCharacteristic(thisCharacteristic) {
            // Enable Sensor Notification
            print( "valid char")
            //                print(thisCharacteristic)

            peripheral.setNotifyValue(true, for: thisCharacteristic)
            if thisCharacteristic.uuid == MagnetometerDataUUID{
                peripheral.readValue(for: thisCharacteristic)


            }
            print("after notify set")
            //                print(self.sensorTagPeripheral.services)
        }
        if SensorTag.validConfigCharacteristic(thisCharacteristic) {
            // Enable Sensor
            print("more valid")
            //                print(thisCharacteristic)
            //                for peripheral in self.sensorTagPeripheralArray{
            peripheral.writeValue(enablyBytes, for: thisCharacteristic, type: CBCharacteristicWriteType.withResponse)

        }
    }

}




func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {

    print(error)

}
//    var streamerTag1 = MyStreamer(fileString: "tag1.txt")
//    var streamerTag2 = MyStreamer(fileString: "tag2.txt")


func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {

    print(characteristic.value!)




    if "\(peripheral.identifier)" == self.tagsIds[0]

    {
        switch characteristic.uuid
        {
        case MagnetometerDataUUID:
            tag1Compensator.getTrimRegisterData(characteristic.value!)

        case IRTemperatureDataUUID:
            tag1Temperature = Double(UInt16(littleEndian: (characteristic.value! as NSData).bytes.bindMemory(to: UInt16.self, capacity: characteristic.value!.count).pointee))

        case IMUDataUUID:
            synchronizer.fillTagArray(characteristic.value!, tag: .first)

        default:
            return

        }
    }
    else if (self.tagsIds.count > 1) && ("\(peripheral.identifier)" == self.tagsIds[1])
    {
        switch characteristic.uuid
        {
        case MagnetometerDataUUID:
            tag2Compensator.getTrimRegisterData(characteristic.value!)

        case IRTemperatureDataUUID:
            tag2Temperature = Double(UInt16(littleEndian: (characteristic.value! as NSData).bytes.bindMemory(to: UInt16.self, capacity: characteristic.value!.count).pointee))

        case IMUDataUUID:
            synchronizer.fillTagArray(characteristic.value!, tag: .second)

        default:
            return

        }

    }
}

}

【问题讨论】:

  • 最好为核心蓝牙管理器使用专用队列。当应用程序进入后台时,主队列被挂起,因此如果您的应用程序处于后台,您的应用程序将停止响应 BT 回调。您还可以在许多 SO 核心蓝牙问题中了解其他一些因素。
  • 我的应用开启了后台模式,在这个模式下可以正常工作,能否提供一些资源?

标签: ios swift multithreading bluetooth core-bluetooth


【解决方案1】:

我总是在后台线程上运行蓝牙活动,因为某些蓝牙 API 调用很可能会阻塞。

我猜想移动到后台的主要候选者是扫描和发现方法,因为那是执行真实硬件操作的地方。

对于我的任务,使用Grand Central Dispatch 就足够了。

EDIT:最简单的 GCD 用法示例:

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
    DispatchQueue.main.async { 
        valueLabel.text = peripheral.value.map { String(data: $0, encoding: NSUTF8StringEncoding) }
    }
}

【讨论】:

  • 您好,感谢您的回复。在peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) 方法中监听更新之类的任务呢?能否提供一个代码 sn-p 你是如何使用 GCD 的?
  • 据我所知,Apple 的工程师也对此进行了沟通,CB API 被设计为异步的。这就是为什么到处都有回调的原因。我个人从未遇到过任何阻塞电话。请您举个例子好吗?
  • @dcdc 我添加了一个示例。
  • @allprog 很有可能它们都是异步的,但我个人在文档中没有遇到任何关于它的提及,所以我认为最好不要做这个假设。至少在后台可以有一些同步代码部分,所以我更喜欢将它全部移到后台,以保持主线程尽可能精简。
  • 您可以确定CB中没有阻塞调用。无需通过异步调用这些 API 来增加应用程序的复杂性。您可以安全地调用初始化程序,或从主队列写入函数。如果在初始化器中使用单独的队列,所有回调都将在那里执行。
【解决方案2】:

尝试创建 blutoothOperation 类,它是 Oeration(NSOperation) 的子类。 操作类及其使用示例参考。

https://developer.apple.com/videos/play/wwdc2015/226/

从以上链接下载示例项目。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-24
    • 1970-01-01
    相关资源
    最近更新 更多