等待 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 的数据,则可以为每个外围设备或服务使用一个简单的状态机。
如果您需要在发现完成后采取一些行动,您甚至可以保留一组未处于完全发现状态的外围设备;例如一旦该外围设备的发现完成,您就可以从集合中删除该外围设备。如果集合为空,则所有设备的发现都已完成。
所有这些状态都属于您的模型。您的委托实现需要做的就是使用已发现的数据更新模型。
在做蓝牙通信的时候,经常会遇到这样的情况,调用在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 的数据,则可以为每个外围设备或服务使用一个简单的状态机。
如果您需要在发现完成后采取一些行动,您甚至可以保留一组未处于完全发现状态的外围设备;例如一旦该外围设备的发现完成,您就可以从集合中删除该外围设备。如果集合为空,则所有设备的发现都已完成。
所有这些状态都属于您的模型。您的委托实现需要做的就是使用已发现的数据更新模型。