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
CBDescriptor instances, but they will all be read-only “proxies” of the
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 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
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.