Bluetooth 2 Logo

Improving an SDK With Core Bluetooth –What’s Going On?

This entry is part 2 of 7 in the series Improving an SDK With Core Bluetooth

Before we dive in, let’s take a bit of time to review how Core Bluetooth handles communications between Centrals and Peripherals.

THE PERIPHERAL MANAGES THE SERVICES, CHARACTERISTICS, AND DESCRIPTORS

As was noted in the previous series, the Peripheral is responsible for setting the values of Characteristics, and managing the lifetimes of Services.

The Central will have CBPeripheral, CBService, CBCharacteristic, and CBDescriptor instances, but they will all be read-only “proxies” of the CBPeripheralManager, CBMutableService, CBMutableCharacteristic, and CBMutableDescriptor instances managed by the Peripheral.

What’s nice, is that Core Bluetooth abstracts all the nitty-gritty of Bluetooth transport for us, and gives us a fairly manageable API.

The Instances the Central Has Are Read-Only “Reflections” of the Peripheral Instances

THE CENTRAL CAN’T TRUST ITS PERIPHERALS, SERVICES, CHARACTERISTICS, AND DESCRIPTORS

Even though the Central has “proxies” of instances managed by the Peripheral, they are not automatically kept in sync with the Peripheral. They are only updated when the Central executes a CBPeripheral.readValue(for:) call. They are not even updated when the Central gets a CBPeripheralDelegate.peripheral(_:, didWriteValueFor:, error:) callback (either the Characteristic one, or the Descriptor one).

NOTE: We aren’t dealing with Descriptors (yet).

This means that the only way for a Central to explicitly, and on-demand, find the value of a Characteristic managed by the Peripheral, is to execute a CBPeripheral.readValue(for:) call, and wait for the CBPeripheralDelegate.peripheral(_:, didUpdateValueFor:, error:) callback. Only then, will the value property be valid.

The other way is for the Central to subscribe to notifications on the Characteristic (as long as the Characteristic has its .notify Property set). It does this by calling the CBPeripheral.setNotifyValue(_:, for:) method. If you have the .read Property set on that Characteristic, then the CBPeripheralDelegate.peripheral(_:, didUpdateValueFor:, error:) callback will provide a valid value.

So that means that we have to have our coupling between the two devices as loose as possible, and be reactive in our approach. We can’t make assumptions, and we have to keep in mind that, if we are a Central, the information that we have on hand may be, at best, dated.

Basically, this is why the only thing that is actually cached, is the CBPeripheral instance in the Central (We have to, in order to maintain a strong reference). We ask it to discover (and maintain) its Services, we ask those Services to discover (and maintain) their Characteristics, and we only check the Characteristic values in the appropriate callback.

Now, let’s start to knock out our punchlist