等待 iOS Swift CBPeripheralDelegate 完成的正确方法?

Proper way to wait for iOS Swift CBPeripheralDelegate to complete?

在做蓝牙通信的时候,经常会遇到这样的情况,调用在delegate中得到响应,例如下图的特征发现:

func discoverCharacteristics(device: CBPeripheral)
{
    servicesCount = device.services!.count

    for service in device.services!
    {
        print("Discovering characteristics for service \(service.uuid)")
        device.discoverCharacteristics([], for: service)
    }
}

现在这个发现不是针对特定设备,而是针对遵循蓝牙 SIG services/profiles 的健康设备,所以我不知道它们可能有哪些服务,也不知道可能有多少特征在每项服务中。该方法是异步的,答案在以下委托方法中发出信号:

// Discovered Characteristics event
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
{
    for characteristic in service.characteristics!
    {
        print("Found characteristic \(characteristic.uuid)")
    }
    servicesCount = servicesCount - 1;
    print("Characteristics sets left: \(servicesCount)")
    if servicesCount == 0
    {
        print ("Found all characteristics")
        DispatchQueue.main.async{
            self.btleManager!.btleManagerDelegate.statusEvent(device: peripheral, statusEvent: Btle.CHARACTERISTICS_DISCOVERED)
        }
        self.device = peripheral
        self.handleMds()
    }
}

现在我需要等到发现完成后才能进行下一步,因为我接下来做什么往往取决于我得到了什么。在 Java 和 Android 中,我所做的是在调用方法中等待 CountDownLatch,然后在回调中向锁存器发出信号以释放该等待。

CountDownLatch 的 iOS 等价物似乎是 DispatchSemaphore。但是,这样做显然会阻塞系统,并且不会调用任何委托。所以我所做的(如上面的代码所示)是用服务数量初始化一个变量 servicesCount,并在每次发出信号时在委托回调中递减它。当它变为零时,我就完成了,然后我进行下一步。

这种方法可行,但看起来很老套;这不可能是正确的。当我需要多次读取 DIS 特性、功能、各种时间服务等时,它开始变得非常混乱。所以我想知道等待的正确方法是什么代表在继续前进之前收到信号? 回想一下,我不知道这些设备可能具有哪些服务或特征。

首先,如果您已经为 Android 实现了 CountDownLatch,您可以为 iOS 执行相同的实现。是的,Swift 没有 built-in CountDownLatch,但是来自优步的好人 created a good implementation

另一种选择是像您一样依赖变量,但将其设为原子变量。有多种在线可用的实现,包括同一个 Uber 库中的一个。另一个例子在 RxSwift library 中。还有许多其他变体。原子变量将为变量的 read/write 操作提供 thread-safety。

但可能最快捷的方法是拥有一个 DispatchGroup。看起来像这样:

let dispatchGroup = DispatchGroup() // instance-level definition

// ...

func discoverCharacteristics
{
    for service in device.services!
    {
        dispatchGroup.enter() 
        // ...
    }

    dispatchGroup.notify(queue: .main) {

        // All done
        print ("Found all characteristics")
        DispatchQueue.main.async{
            self.btleManager!.btleManagerDelegate.statusEvent(device: peripheral, statusEvent: Btle.CHARACTERISTICS_DISCOVERED)
        }
        self.device = peripheral
        self.handleMds()
    }

// ...

func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
{
    // ...
    dispatchGroup.leave()
}

也就是说,你是在提交请求的时候进群,在处理完请求的时候离开的。当所有项目离开组时,notify 将使用您提供的块执行。

每次调用 device.discoverCharacteristics,您只会调用一次 didDiscoverCharacteristicsFor。也就是说,didDiscoverCharacteristicsFor 调用仅在服务的所有特征都被发现后才会进行。

您已将 peripheral 传递给委托调用,因此您拥有了解委托调用上下文所需的信息。没有必要“等待”。如果您同时发现多个 peripherals/services 的数据,则可以为每个外围设备或服务使用一个简单的状态机。

如果您需要在发现完成后采取一些行动,您甚至可以保留一组未处于完全发现状态的外围设备;例如一旦该外围设备的发现完成,您就可以从集合中删除该外围设备。如果集合为空,则所有设备的发现都已完成。

所有这些状态都属于您的模型。您的委托实现需要做的就是使用已发现的数据更新模型。