CoreBluetooth: How to design code for many characteristics (30 - 40)?
Asked Answered
P

1

10

I searched around a bit and just found this as a possible duplicate question:

Multiple CBPeripheral's for same device

My problem is:

I have multiple services which all together have about 30-40 characteristics (Yes, I need all of them...). As starting point for dealing with CoreBluetooth I always used the Apple Sample Code (CoreBluetooth Temperature Sensor).

Discovery and Service/Characteristic handling is divided into two classes and this works fine for just a few characteristics. But handling this huge amount of characteristics in one class is not what I understand under "good software-design".

The first idea that come into ones mind is to create one class for every service. But unfortunately a CBPeripheral just can have one CBPeripheralDelegate at the same time. This means I can't divide it up into several classes.

(We don't have to start the discussion if BLE is the right technology for getting this amount of data - it isn't. But there are manufacturers that use BLE so they don't have to hassle with the MFi program...)

I also read the finally provided CoreBluetooth Programming Guide but it just describes basic workflows - nothing about the right design.

I'm looking for a nice design approach. You may have any suggestions, hints or links to sample code? Many thanks in advance!

Pandanus answered 19/9, 2013 at 14:21 Comment(0)
P
9

Breaking out logic into several self contained classes is always good design. You should definitely try to group your code according to services or other categories. Even though the peripheral has only a single delegate, you can easily implement the dispatcher pattern where you register the various service implementations and selection keys (practically the service objects) and dispatch the call to the designated service handler. If the service classes implement the CPPeripheralDelegate protocol, then this design will allow you to test/reuse each service separately if you need to with minimal changes in the code.

In pseudo obj-c code the dispatcher peripheral delegate would look like as follows:

// The ivar/property serving as the registry
NSMutableDictionary *registeredHandlers = [[NSMutableDictionary alloc] init];

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
  // for each service create an instance of its handler class and
  // add them to the registered handlers
  for (CBService *service : peripheral.services) {
    if (!registeredHandlers[service]) { // don't reinitialize if not needed
      ExtendedCBPeripheralDelegate *serviceHandler = [self instantiateHandlerForService:service];
      [registeredHandlers setObject:serviceHandler forKey:service];
      [serviceHandler discoverCharacteristics]; // make this functionality self contained for the service
    }
  }
}

In service or characteristic related callbacks the dispatching should be implemented. An example:

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
  ExtendedCBPeripheralDelegate *serviceHandler = registeredHandlers[service];
  [serviceHandler peripheral:peripheral didDiscoverCharacteristicsForService:service error:error];
}

- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
  ExtendedCBPeripheralDelegate *serviceHandler = registeredHandlers[characteristic.service];
  [serviceHandler peripheral:peripheral didWriteValueForCharacteristic:characteristic error:error];
}

If the central manager is powered off, then the best solution is to drop the whole peripheral delegate. Don't bother with reinitialization, rather plan for disposal. Of course, if needed, you can notify the service handlers of the imminent destruction.

Phloem answered 19/9, 2013 at 22:45 Comment(4)
First thanks for the nice explanation. Just for knowing if I understand right: The delegate remains in the class that manages the several service handlers. Where would you store the retrieved values? In each service handler or in the manager class? Would you use global NSNotifications for informing you view controllers about the newly retrieved values?Pandanus
Yes, you modify the original delegate to implement the dispatcher pattern. You can store the references anywhere, just make sure that the service handlers can access them when needed. Keep in mind not to try to cache them, Core Bluetooth will do it for you when possible. Using notifications is one way to implement loose coupling but I prefer to use Signals from ReactiveCocoa. It's an amazing library! Check out the repo and read the docs github.com/ReactiveCocoa/ReactiveCocoa If you have any questions, open an issue or post it on SO. You'll love it once you get used to it.Phloem
This is a fantastic answer, thanks for taking the time to write it.Weisman
One side note: I remember that when I used NSNotifications with this amount of data, this did end up in a freezing app. Notification storms are really no good idea for transferring data...(long time ago I did this, but I thought this is still an important message for any iOS newcomers)Pandanus

© 2022 - 2024 — McMap. All rights reserved.