How to trigger Core Bluetooth state preservation and restoration
Asked Answered
L

2

11

State Preservation and Restoration Because state preservation and restoration is built in to Core Bluetooth, your app can opt in to this feature to ask the system to preserve the state of your app’s central and peripheral managers and to continue performing certain Bluetooth-related tasks on their behalf, even when your app is no longer running. When one of these tasks completes, the system relaunches your app into the background and gives your app the opportunity to restore its state and to handle the event appropriately. In the case of the home security app described above, the system would monitor the connection request, and re-relaunch the app to handle the centralManager:didConnectPeripheral: delegate callback when the user returned home and the connection request completed.

How can I trigger this and test the code?

I have got an accessory with a service. I have got an app that scans for the service and I opted in state preservation. However I am not sure how to logically test it as I do not know what I need to trigger it. These are the options that I tried unsuccessfully:

A - kill the app from Xcode

B - kill the app manually

C - power off the phone

D - something else

In all these options I tried to go to Xcode -> device and look at the logs, but haven't seen any state restoration logs.

Thanks

Lemuellemuela answered 14/10, 2015 at 15:57 Comment(0)
C
22

NB Thanks to user1785784 for sharing Apple's QA1962 - Conditions Under Which Bluetooth State Restoration Will Relaunch An App which describes new Bluetooth behaviour in iOS 11. This document should be considered the answer to this question, although I think it incorrectly claims that iOS 10 would relaunch an app that has been force quit. (I haven't tested that on an iOS 10 device, but it would have been a departure from iOS 9. Can anyone confirm?).

Killing the app manually (B) from the task switcher, ensures your app will not be launched automatically until the user explicitly opens it again.

C doesn't work either, I think only VOIP apps are launched automatically after restart, and then only after the device is unlocked.

I don't know any D.

I use A.

First, to implement Bluetooth State Restoration, make sure you've

  1. added bluetooth-central as a UIBackgroundModes to your Info.plist
  2. set a CBCentralManagerOptionRestoreIdentifierKey when initing your CBCentralManager
  3. implemented the -(void)centralManager:willRestoreState: callback in your CBCentralManager delegate.

Then you're ready to test state restoration:

  1. get the app to some known state (say bluetooth powered on, some device connected/connecting)
  2. kill the app in Xcode
  3. watch the logs or set a launch breakpoint
  4. make a change in the bluetooth state, e.g. by
    • toggling airline mode
    • taking a bluetooth device out of range (to avoid walking, I put mine in a conductor/Faraday Cage/coffee pot)
    • bring the device back into range
    • interacting with device, e.g. by pressing a button/having a pulse
  5. watch your state restoration code be called

NB: application:didFinishLaunchingWithOptions: will be called first, and you must immediately init your CBCentralManager as described above. Then centralManager:willRestoreState: will be called.

A coffee pot that is also a Faraday Cage

Cookshop answered 15/10, 2015 at 0:23 Comment(11)
@RhytmicFistman Thanks a lot for the answer.. I am using your approach. However I am not sure if I implemented the state restoration code correctly. Do I need to instantiate a new CBCentralManager object in AppDelegate didFinishLaunchingWithOptions? Would you be please able to share some simple sample code ?Lemuellemuela
PS: Also whenever I use NSLog I don't see the logged messages in the Device logs (when the app is killed). I can only see things like "BTLE scanner Powered On" etc..Lemuellemuela
sorry, I forgot to mention that you need to add a background mode to your Info.plist. answer updated.Cookshop
Note that per the link provided below by @user1785784 toggling Bluetooth Power does not trigger app restart, but toggling Airplane Mode does.Salesperson
What do you mean this point ? "interacting with device, e.g. by pressing a button/having a pulse"Needs
If the device has a button, press it - if the device is a wearable that monitors your heart rate/pulse and updates a characteristic value ever time your heart beats, then wear it. In other words use the device for its intended purpose, and make it do something.Cookshop
@RhythmicFistman I have a peripheral connected, I turn the bluetooth off. After a while when the app is suspended, I turn it on, ideally it should call my restoration method. It doesn't, when I launch the app manually, the restoration method is called and the peripheral is in connecting state. However, if I don't turn the bluetooth off and come back in range even after 8-9 hours, my app is launched in background and gets connected. Do I need to do something different while toggling bluetooth on and off?Sverige
That should work. Does it work with airline mode? What iOS version are you using?Cookshop
I'm kind of late to the party. However, you can also kill the app programmatically by running the following: exit(0). This should simulate the OS killing the app. If your device then discovers a perisperhal that it was scanning for before it was killed, it should get relaunched. To see the logs, I use os.log (Unified Logging). This allows me to view the logs in the Mac Console application. You can give your logs a tag for which you can filter in the console.Companionway
To log using OS Log, you first have to import os.log and then call the following: let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "background-scan") os_log("Your message", log: log) The category is what you filter by in the Console appCompanionway
centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) I have get CBPeripheral type of array .how to scan new devices in bacground modeCategorical
S
5

Just figured this out recently with the help of Apple Tech. Also given/have a nice link that shows the different ways to cause the app to restart without user intervention.

I did it by causing the app to crash suddenly with the following snippet of swift code. This causes the app to restart and the call the 'willRestoreState' callback.

DispatchQueue.main.asyncAfter(deadline: .now() + 5)
        {
            print("Killing app")
            // CRASH
            if ([0][1] == 1){
                exit(0)
            }
            exit(1)
        }
Sylvia answered 15/10, 2017 at 17:10 Comment(2)
Thank you for this very important link!Cookshop
This is indeed an insanely important link. Thanks you.Cothran

© 2022 - 2024 — McMap. All rights reserved.