In this, the third and final installation of the Zero to BLE on iOS trilogy, the main topics we’ll consider here are Disconnection, Reconnection, Backgrounding and State Preservation and Restoration. That’s a lot to cover, but thankfully a couple of those are fairly easy to implement because much is handled by the Core Bluetooth framework and iOS behind the scenes.
As an added bonus, I’ve included in the sample code an example of how to turn your iOS Device into a Bluetooth Low Energy Peripheral, which is great for prototyping and testing. I don’t discuss the mechanics of how to do it in the article, because that is by far the less common scenario when developing apps for BLE and iOS. However, it’s a core function of the demo app and you can adapt it to your needs.
My goal with the Zero to BLE on iOS primer is to help iOS developers get started developing for Bluetooth Low Energy (BLE) devices on iOS using the Core Bluetooth framework. If you haven’t read the first two parts yet, I encourage you to read Part One and Part Two – Objective-C or Swift version – first, and then come back for part three.
##Picking Up Where We Left Off…
In Zero to BLE on iOS – Part Two we discussed the mechanics of getting started with Core Bluetooth, how to scan for devices, and how to connect once we found the device we’re looking for. In this part of the series, we’ll consider some advanced topics that are good to know once you have the basics down.
##The Project
Before we get into those topics, I highly encourage you to go to the Github repo for this article and clone or download the code for this article. You’ll find it useful to follow along in the code because I won’t be able to include it all in the body of the post. You’ll get richer context by also examining the sample code as well.
The demo project is inspired by a relatively hard-to-find sample app created by Apple that seems to be the basis for the one they used in one of the WWDC demos. It was when I was reviewing the WWDC videos in preparation for this article when it hit me that the demo app that they put together demonstrated a few things that I wanted to show in this part of the series, so building on what they had done made sense.
The first trick was finding it. Unsurprisingly, the app was last revised in 2012, so the original app project was written in Objective-C. So this gave me an excellent opportunity to just rewrite the thing in Swift. I further modified it because I didn’t particularly care for the way that it was handling its connections. Therefore, while some of its behavior is similar to the original, this app behaves slightly differently, and goes beyond what Apple’s demo accomplishes so that I could demonstrate additional aspects of Core Bluetooth.
##Disconnection
The first thing we should talk about is disconnection. I noticed that I somewhat glossed over the topic in Part Two, so I thought that might be a good place to pick up and give some more detail.
In its simplest form, if you have a CBCentralManager instance created and you also a reference to the CBPeripheral you wish to disconnect from, then you use the CBCentralManager’s cancelPeripheralConnection(_ peripheral: CBPeripheral)
method.
However, you may have a situation in which you may be connected to a peripheral and you are currently subscribed to one or more of its characteristics, receiving periodic notifications about changes in their values. In that scenario we would need to do something a little more involved, but feels pretty familiar since we’re already used to looping!
Let’s walk through this. It looks like a lot is going on, but it’s actually pretty straightforward.
First we check to make sure we even have a peripheral object created (1), and if not, we just bail out right there. Then we check to see if the peripheral is connected (2). If it’s not connected, we set our reference to nil, just to clean up a bit, and iOS will also dealloc the CBPeripheral object, if present (this technique can also be used on the Peripheral side to disconnect, since there is no method on CBPeripheral to disconnect directly).
If we have not discovered services (3), the CBPeripheral
’s services
will be nil, and there’s not much more we can do than just directly by calling the CBCentralManager
’s cancelPeripheralConnection
method. Next we (4) loop through the characteristics and see if any of them match the UUID of the Transfer Characteristic we set up in the Device struct. If the characteristic is present (which we assume it is, but we do the looping just to be sure), we (5) unsubscribe from the characteristic. This sets off a chain reaction in which the CBPeripheralDelegate’s didUpdateNotificationStateForCharacteristic
method is called.
Also notice in the example above (6) that if we didn’t find a characteristic that matches, we use the CBCentralManager
’s cancelPeripheralConnection
method to disconnect from the peripheral.
Looking at the CBPeripheralDelegate
’s didUpdateNotificationStateForCharacteristic
delegate method implementation:
We see that when didUpdateNotificationStateForCharacteristic
is called, we (1) check to see whether the characteristic is currently notifying our central of its value. In the case where we earlier called setNotifyValue
to stop receiving notifications of changes to the characteristic, its isNotifying
property is false
, and so we drop into the else portion and (2) disconnect by calling the CBCentralManager
’s cancelPeripheralConnection
method.
###Guarding from The Pyramid of Doom
I’d like to just take a moment and point out that we’re using Swift’s guard
statement a couple of times here. You’ll probably find that guard
is totally awesome, and it saves us from The Pyramid of Doom right from the beginning of our disconnect method. We still have to do a little bit of pyramid action later on with the looping, but it’s not crazy deep like it would have been without guard
.
##Required Viewing Before we get into that, however, I highly encourage anyone who is doing any sort of Core Bluetooth work to watch three WWDC videos which explain some of the concepts fairly well…:
- WWDC 2012 - Core Bluetooth 101
- WWDC 2012 - Advanced Core Bluetooth
- WWDC 2013 - Core Bluetooth
I listed these as references in Part One, but now they’re required viewing, and you may wish to watch them a couple times and take some notes because there are some hidden nuggets of information contained therein.
I’ll try to simplify some of the concepts, however.
##Reconnection OK, so what happens when you want to reconnect at some time in the future? Here’s where things get interesting.
As it turns out, to reconnect to a device, all you have to do is start scanning again.
Wait… what?
Personally, I think I was overanalyzing the situation when I first started working with Core Bluetooth and as a result, the reconnection process seemed baffling. However, Apple actually developed the framework to be as low-friction as possible, and what this means is that when you disconnect from a device, you can immediately call the CBCentralManager
’s scanForPeripheralsWithServices
method again. As the Apple engineers describe it in the WWDC sessions, at some future point in time your app will reconnect. It’s very clever when you think about it, and it’s the ultimate asynchronous behavior.
What this means is that your app can begin scanning again, and whether it’s a second, a minute, an hour, a day, a week, or a month in the future, your app will connect to the device when it encounters it. This opens up the way for a number of interesting possibilities if you consider how much control you have and how relatively easy it is to control the process.
##Backgrounding The next topic that we didn’t cover in the other parts is Backgrounding, or rather, discussing what your app can and can’t do while in the background with BLE. Again, the answer is relatively simple, but there are some caveats as there are with many things when discussing the topic of what capabilities an app has while in the background.
In typical Apple fashion, they make backgrounding pretty easy to accomplish. The first thing you need to think about, however, is “In what capacity will my app be functioning?” In other words, will the app be functioning as a Central, a Peripheral, or perhaps both? In the case of the demo app that I built for the article, it’s both. If you’re building an app for the world of IoT, your app will probably be a Central that interacts with a physical BLE device.
Once you’ve decided which mode your app will run as, then all you need to do is add some flags to your Info.plist:
- Find your Info.plist file in your Xcode project
- Control-click somewhere in the list and select “Show Raw Keys/Values.”
- After showing the raw values, add a new section called
UIBackgroundModes
. - If your app will be functioning as a Central, add the
bluetooth-central
- If your app will be functioning as a Peripheral, add the
bluetooth-peripheral
When you run your app, it will be able to do some things that it was not previously able to do while in the background. For example, if you use the bluetooth-central
, the following will take place per Apple’s documentation…
The first thing that happens is that the CBCentralManagerScanOptionAllowDuplicatesKey
scan option key is ignored.
We didn’t really talk about this in previous articles when discussing scanning, because the default behavior is usually sufficient for our needs. However, when scanning for peripherals using scanForPeripheralsWithServices(_:options:)
, one of the keys for options you can pass in the dictionary in the second parameter is CBCentralManagerScanOptionAllowDuplicatesKey
, and the value can either be true
or false
. If the value is true, no filtering will occur during discovery, and a discovery event is generated each time the central receives an advertising packet from the peripheral. This results in greater battery usage, so it really should be used with care – used pretty much only if you know what you‘re doing and actually want that behavior.
On the other hand, if the value is set to false
, then multiple discoveries of an advertising peripheral are bundled up into a single discovery event instead of multiple discovery events. Very often – and wisely – Core Bluetooth opts for the methodology that will a) make your app a Good iOS Citizen, and b) helps it conserve battery life by default when possible. Unsurprisingly, the default value is false
, so actually the background behavior matches the default behavior when the app is in the foreground. Therefore, we really don’t have to worry about that too much, unless you really need the agressive discovery option (most of us won’t need it).
The other thing that iOS does in your behalf may have a little more impact on your app, and it may not make you too happy initially. However, if you consider what I mentioned earlier about Apple’s defaults, the action taken when your app is in the background will make sense. If iOS deems it necessary – for example when all other apps that are scanning for devices are backgrounded – it will increase the interval at which your central device scans for advertising packets. Because the scan interval determines how often your app scans, increasing the interval makes it slower. Consequently, it may take longer to discover an advertising peripheral while your app is in the background.
These changes that go into effect focus on minimizing the device’s radio usage and are designed to improve battery life – or at least not drain it quite so quickly if it was doing everything at full speed.
And here’s the cool part… it works! Take a look at this snippet of the log from the demo app running as a Central (ellipses indicate edited parts to make it more succinct, and I added the numbers for the explanation below):
Central Manager State Updated: CBCentralManagerState Scanning Started! // 1 Discovered Optional("iPhone 5c") at -33 Device is in acceptable range!! // 2 Connecting to peripheral: <CBPeripheral: 0x13e5bc480, identifier = 5F72478A-06DE-D36D-BB5F-C0E072144CCF, name = iPhone 5c, state = disconnected> Peripheral Connected!!! Looking for Transfer Service... Discovered Services!!! // 3 Discovered service <CBService: 0x13e542340, isPrimary = YES, UUID = E71EE188-279F-4ED6-8055-12D77BFD900C> // 4 Notification STARTED on characteristic: <CBCharacteristic: 0x13e5bc050, UUID = 2F016955-E675-49A6-9176-111E2A1CF333, properties = 0x10, value = <7b7b7b45 4f4d7d7d 7d>, notifying = YES> ... didUpdateValueForCharacteristic: 2016-08-29 23:44:17 +0000 Bytes transferred: 6 Next chunk: Device Next chunk received: Device Transfer buffer: Optional("Enter your message here to be transmitted via Bluetooth Low Energy to a Central Device") didUpdateValueForCharacteristic: 2016-08-29 23:44:17 +0000 Bytes transferred: 9 Next chunk: {{{EOM}}} Final message: Enter your message here to be transmitted via Bluetooth Low Energy to a Central Device
Immediately after turning on the Central mode functionality in the app, I backgrounded the app. As you can see from the log, even while in the background, the app discovers the device (“iPhone 5c”) running the app in Peripheral mode (1). It also connects to the device (2), discovers the Transfer Service (3), subscribes to it, and begins to receive notifications of changes (4) – all over Bluetooth Low Energy while the app is running in the background!
In the demo app I created, having the Peripheral side in the background doesn’t quite make sense because the app allows you to type text and the changes get sent to the Central as you type, so in this case, the app has to be in the foreground. However, if it was monitoring step counts or location or something like that it could still feed that information to the Central while backgrounded (subject to any of iOS’s limitations on those other functionalities working in the background, but you get the idea…).
So that’s backgrounding. Like so many other aspects of Core Bluetooth, a lot is really just handled for you, so if you need it, then take advantage of it. I have noticed that while in the background I don’t get all the updates that I expect, so you may need to bulletproof against that on the device side. For instance even with the demo app I noticed that in the background sometimes it would transfer the initial block of text, sometimes not. However, when a change occurred on the Peripheral side, it reliably responded to the changes.
##State Preservation and Restoration
There’s another aspect of iOS and Core Bluetooth – and this may really blow your mind as it did mine when I found out that it had the ability to do this – and that’s in the area known as State Preservation and Restoration.
Imagine a situation in which you have an app that goes into the background, and then it gets squished out of existence due to memory pressure. Under a normal app situation, you would pretty much have to pack up your things and go home. If your app isn’t running, then there’s no real way for you to anything after it gets squashed.
However, starting in iOS 7, the Core Bluetooth engineers did something pretty amazing. Even if your app gets terminated due to memory pressure or because it’s your turn to get bumped, iOS jumps in and acts as application proxy for you.
What does that mean?
Well, it means that when your app is terminated, iOS takes over all the Bluetoothy things your app was doing while it was working away in the background. When that happens, iOS takes snapshot of all the Bluetooth-related objects and activities that were going on in your app.
For example, if you have a CBCentralManager
that’s discovering/scanning peripherals, any active (and even pending) connections, and any subscriptions to peripheral characteristics get taken over by the operating system and keep working in a headless (or is it bodyless?) fashion until your app is needed later.
The same goes for CBPeripheralManager
s too. If you have a CBPeripheralManager
in your app that is advertising, has published services, or has subscriptions that a CBCentral somewhere has subscribed to, iOS will take those over on your behalf as well.
Pretty cool, huh?
Then, if iOS needs your app to give attention to something, your application is re-launched in the background, it’s allowed to run for a short period of time (an unspecified amount, but if it’s anything like silent notifications, you might get about 10 seconds, but don’t quote me on that – I couldn’t find an official number in the documentation).
Once your app is brought back to life, it rehydrates any CBCentralManager
and CBPeripheralManager
objects that were active when your app got the axe. Then, there’s a delegate method that gets called that you must handle that gives you all preserved state that was captured. It’s your job to re-wire everything once that delegate is called, however.
The amazing thing about this process is that, yet again, much of it is handled by iOS and the Core Bluetooth framework. You’re insulated from all the gory details about what is going on under the hood. Other platforms may have more control and flexibility, but personally, I like Apple’s strategy for developers. It’s clean, simple, straightforward, and it works.
###Implementation
Fundamentally, State Preservation and Restoration only requires you to do two things:
-
Create a Restore Identifier for the
CBCentralManager
orCBPeripheralManager
objects you wish to be taken over by the operating system when your app gets terminated. -
Implement one particular delegate method for your manager objects (so you may have two if you are implementing
CBCentral
andCBPeripheral
objects)
Let’s see how that’s done.
First, the Restore Identifier. This is the easiest of the tasks. As you may recall, we previously instantiated a CBCentralManager
with the following code:
Now, to get State Preservation and Restoration in the mix, we initialize it slightly differently:
The key is the dictionary that we pass to the options. We pass CBCentralManagerOptionRestoreIdentifierKey
as the key, and the value is the Restoration Identifier that we create for this purpose. The Restoration Identifier is not special. It’s just a string value we pass in the options dictionary so that iOS can keep track of the object for us while we’re gone (i.e. while we’re terminated). The only important detail is that the string must be unique enough so that it will not collide with any other app’s identifiers. So I use a reverse-domain-name notation to increase the odds of uniqueness. I’m using “io.cloudcity.BLEConnect.CentralManager
” in the example.
The second part is implementing the delegate method, so let’s get into that now.
A key point to remember with State Preservation and Restoration is that the centralManager(_:, willRestoreState)
delegate method that gets called when your app is resurrected actually gets called first. That means it gets called even before centralManagerDidUpdateState
(or peripheralManagerDidUpdateState
in the case of using CBPeripheralManager
), which is usually the first method that gets called when you fire up a manager object. However, when you opt-in to State Preservation and Restoration as we described above, then you’ll need to implement the new delegate method.
The first thing to notice is that we get a dictionary full of state information. This holds things like an array of service UUIDs for services the central manager was scanning for (retrieved with the CBCentralManagerRestoredStateScanServicesKey
key) and peripheral scan options that were being used by the central manager at the time the app was terminated (retrieved with the CBCentralManagerRestoredStateScanOptionsKey
key). For our purposes, we don’t use those because we are always scanning for a device. However, they are there in case you need them to kick off a new scanning session.
The dictionary also holds something we do need, and that’s an array of CBPeripheral
objects that were connected to the central manager (or that had a connection pending) at the time the app was terminated by the system. When possible, all the information about a peripheral is restored, including any discovered services, characteristics, characteristic descriptors, and characteristic notification states.
To make use of that information, we first get a reference to the object in the dictionary that contains the array using the CBCentralManagerRestoredStatePeripheralsKey
key (1). Once we have that, technically we don’t really know what it is yet so we need to cast it into an Array
of CBPeripheral
objects (2). Next we check to see if the array actually contains anything (3), and if so, we grab the first one since we don’t want to be connected to more than one device in this app. Your mileage may vary, however, since you may want to connect to multiple devices – in which case you would probably have an array of references that you would populate directly from the array that the state dictionary provides you.
###But Wait! There’s More!
That’s it for the required pieces. You may be able to get by with just those. However, there are a couple of other things you can do to improve the robustness of your app. To do this, we check to see if we are already connected to the CBPeripheral that we received when we were restored (prepare for a very long block of code here…).
The way we accomplish that is by first checking to see if we have a peripheral object already (1). If not, the guard statement does its job and exits right there. Then we check to see if the peripheral is connected (2). If it’s not connected, we exit with the help of the guard statement. If it is connected, we continue on.
The next question we need to know the answer two is: “Even though I’m connected to the device, am I actually subscribed to the Transfer Service?”, so the next step is to see if the peripheral has any services (3). Again, if it doesn’t, we exit.
If it does have services, we go to the final step in this phase and see if the peripheral’s services contain the UUID for the Transfer Service (4). If the service we’re looking for with the UUID of Device.TransferService is found, we proceed with checking our characteristic subscription (see below). However, if it’s missing, then we tell the peripheral to begin discovering the service (5 – it’s at the bottom).
As was already mentioned earlier, our next step is to determine if we have subscribed to the Transfer Characteristic. Once we know we have a reference to the service, then we inspect the services characteristics and see if the transfer characteristic is contained in the services characteristics array (6). If the characteristic is present, we also check to see if it we are currently subscribed by checking the characteristic’s isNotifying
property (7). If it is not currently notifying us, we use setNotifyValue
to begin the subscription process – just like we did in the standard workflow. However, if we did not find a characteristic in the services characteristics array, we go ahead and tell the peripheral to begin discovering characteristics for the transfer service (8).
That is pretty much all there is to it as far as the code in the managers is concerned, and this example just gives you the basic idea of how to handle State Preservation and Restoration. In more complex applications and with more complex devices, you may need to check for multiple services and multiple characteristics.
###Additional Case There is one more that may need to handle. In our case it’s pretty straightforward because we only have the one central and peripheral that is alive at any given time. But there may be some apps that create and destroy multiple instances of centrals and peripherals, and they may not have a well-defined lifetime.
That’s when we get our old friend app delegate involved. This is necessary because it’s in didFinishLaunchingWithOptions
that we get a list of the UUIDs that represent all of the CBCentralManager
and CBPeripheralManager
objects that were active when your app was terminated and that Core Bluetooth and iOS took over while you were terminated.
What you need to do at that point grab the launch options (1), get the array containing the IDs (2). In this case, we use UIApplicationLaunchOptionsBluetoothCentralsKey
to get the keys to any centrals we may have instantiated before being zapped. Then, since it’s just an array of strings, we loop through them, looking for the one that matches the Restore Identifier(s) that we’re interested in.
As a side note, if you choose to not restore a manager, the system will let it live for a brief time and then will get rid of it, which makes sense.
In the demo app, I only demonstrate these with the Central Manager, since that is by far the more common and likely scenario for most developers. The hardware engineers you work with will be handling any of those issues they deem necessary on the device side. If you wish to learn more about State Preservation and Restoration, please consult Apple’s documentation on the subject. It’s pretty dry reading but they do cover all the details mentioned here, plus they also address the issue of restoration of an app that restores CBPeripheralManager
s.
##Caveats It should be mentioned that this is all very impressive when it works. Unfortunately State Preservation and Restoration has somewhat of a reputation for being unreliable. I haven’t found it to be the case but I’ve also not relied on it as heavily as others may have. Therefore, there are some things you should be aware of before committing to using State Preservation and Restoration.
For starters, while it is not explicitly stated in the documentation or the WWDC presentations, it is implied that this works only when you’re app has been terminated by the system. What this means is that if a user does the “double-tap on the home button and swipe up” dance to terminate your app, all bets are off. State Preservation and Restoration does not work under that condition – it only works when the system kills your app and then needs to bring it back.
Another issue that you may encounter is the situation in which Bluetooth itself gets turned off. This could happen either by the user disabling it in Control Center or in iOS Settings, or iOS could shut it down in the case of a device reboot. In those cases State Preservation and Restoration isn’t going to help you because, unsurprisingly, all of the connections get flushed down the toilet when those events happen (imagine what mayhem would ensue if it didn’t do that).
##Recommendations
While the apparent magic of State Preservation and Restoration is pretty amazing, one should really consider if it’s something you will need. Naturally there are circumstances and requirements that will cause you to need it. However, if it’s not a requirement – and I suspect for many developers it won’t be a requirement – I would recommend that developers use the least amount of functionality as is necessary.
That said, if you do need the functionality, it’s there for you to use. Just remember that there are caveats and you may need to do a lot more work to support those issues with your end users than you would otherwise if you avoid the advanced features like backgrounding and state preservation.
##The Journey Ends and Continues
Well, this is the end the three-part Zero-to-BLE series, and I thank you for taking the time to read it and hope that it’s beneficial in clearing up some of the more confusing aspects of developing for Bluetooth Low Energy devices on iOS.
There are more topics that you can continue to research on your own, too. For example, we did not get the chance to discuss Security and Encryption, which core Bluetooth does support. If you watch the videos that I referenced at the beginning of the article from WWDC 2012 and 2013, they provide the information that you need to at least get started on that topic.
Additionally, if you wish to really get serious about Core Bluetooth and you have tough questions that need to get answered, then I highly encourage you to join the Core Bluetooth mailing list hosted by Apple. If you join that mailing list, you’ll be able to post questions and get them answered by very knowledgeable engineers both inside and outside of Apple who have vast experience in the Domain of Core Bluetooth as well as Bluetooth classic.
It’s apparent that the Internet of Things train has left the station and it’s moving along at full speed with no sign of slowing down. Now’s a great time to get involved in developing apps that use Core Bluetooth, and the flood of new products that we can use to create awesome devices doesn’t seem to be stopping anytime soon.
##What’s Next?
I sincerely hope you’ve enjoyed this series of articles as much as I’ve enjoyed putting them together, and I’m looking forward to putting together more fun and instructive blog posts for Cloud City Development in the future.
In the meantime, if you’d like to work with me or any of the other awesome designers and developers here at Cloud City Development please do not hesitate to contact us.
If you’ve got a mobile app project that you need help getting off the ground and you need top-notch design and engineering skills, if you need an API backend for your app, or even a complete web app solution, Cloud City Development can take care of all of it for you.
You’ll be in good hands and we love working with nice people who have great ideas!