Objective-c long-running background service for bluetooth, when app backgrounded - ios

I can't seem to find the right documentation, tutorial or SO post to point me towards the right implementation so far... even though there are many posts about this topic floating around.
Problem
I have an app that connects (pairs) to a BT device I built. What I want, is when the user presses the home button or backgrounds the app I want to send an update through a BT service layer to a BT device on a regular interval. I am doing this now (only fires once) in the applicationWillResignActive and applicationDidEnterBackground methods and it works perfectly. The problem is that I can only perform this once it seems. Any loops, timers, background type services that I start up in these methods, die very soon there after.
What I need
Is for the application to keep looking for my BT device in the background and every 10 seconds or so and send an update to the device. I first need to check to make sure the app is still backgrounded, but once I know its in the background I wanna send the update. When the app comes back in focus I can stop the updates to the BT device but its crucial that it sends them when the app is not in focus.
Research
I have found these SO posts (long-running tasks, background task execution handler, using this in an NSTimer loop... but it dies) but they have not helped with implementation at all.

When I wrote this post I was far too new to OBJ-C and didn't quite understand the concept of delegate methods with the proper access level to fire in the background (i.e. info.plist access under bluetooth-central).
The end result a month later was to build a peripheral device that could wake up the application in a timed loop via subscribed characteristic updates. Once the app wakes up, you have 10 seconds to handle the event that has just occurred and since I am only saving off a copy of the data that the device posted, all is well.

Related

iOS : Use of HKObserverQuery's background update completionHandler

HKObserverQuery has the following method that supports receiving updates in the background:
- initWithSampleType:predicate:updateHandler:
The updateHandler has a completionHandler which has the following documentation:
This block is passed to the update handler. You must call this block
as soon as you are done processing the incoming data. Calling this
block tells HealthKit that you have successfully received the
background data. If you do not call this block, HealthKit continues to
attempt to launch your app using a backoff algorithm. If your app
fails to respond three times, HealthKit assumes that your app cannot
receive data, and stops sending you background updates.
From looking at other posts it seems like there's a lot of confusion revolving around this handler. Below are some questions that I have about it:
When should the handler be called? If called too late, then HK might think that the app never received the query update causing you to hit the background update 3-strikes back-off algorithm. The documentation states that it should be called after handling other queries. Depending on how long it would take to run those queries, it sounds like you could get dangerously close to hitting the background update strikes.
Why is this needed? Shouldn't the system know that the app has been launched and has received the background update? When using CoreBluetooth in the background it just wakes your app up in the background for 10 seconds. No need to call any handler or deal with the background update 3-strikes.
If you hit the background update 3-strikes and HK stops sending updates is that permanent? Does HK ever start sending the background updates again? What if there's a bug that prevented the handler to be called and now you've fixed it. Is the app stuck never receiving the updates? Or will it reset when the app is re-launched or updated?
Does HK keep your app running in the background until the handler is called? Is that part of its purpose or just a side effect? If it's part of its purpose how long can we run before needing to stop (and hit the first background update strike)?
When should the handler be called?
Call it after you are done your job. Your code should not do complex operations. The app is in the background and the user does not see what's changed. You can just set a "flag" that data is updated and do complex operations after the user launched the app. If your decision about either notifies the user or not based on complex operations, then try to refactor code so that all necessary data is pre-calculated (e.g. in UserDefaults) and extra data is simply fetched with that data. So, 1-2 seconds is enough for your calculation.
Why is this needed?
All such handlers have completion closures. They are needed for iOS to know if your app works fine. If your app will eat too much CPU time, then iOS could become slow. Hence, Apple wants to be sure that iOS works fine despite bad apps.
If you hit the background update 3-strikes and HK stops sending updates is that permanent?
No.
Does HK ever start sending the background updates again?
Yes. But it depends on many factors. It may try to call your app again in 1-2 days. If nothing changes it will call it rarely.
Does HK keep your app running in the background until the handler is called?
This is unknown. It depends on many factors. Probably if iPhone is charging it will allow running your app longer just to estimate if the completion handle is called or not. If your iPhone is not charging and closed to 0% battery, then more likely iOS will kill your app. So, you should not do any job after you called the completion handler. And try to keep it simple.
Recommendations
You should process new data as quickly as possible. If you need to fetch a lot of data, then try to optimize this and pre-calculate it when the app is in foreground, then save somewhere (UserDefault), and use new data with cached data to make a decision (e.g. notify user about something; I believe you need background updates exactly for that).
1-2 seconds or less is a good time for background updates.

Is the appPaused event working properly for me? I am getting some weird behavior with my clean-up code on iOS

I have been dealing with this issue for weeks and am unsure if it is my code's fault, forge's appPaused event not triggering quickly enough, or Trigger.io's documentation not being nearly clear enough about just how little time iOS gives us to execute clean-up code.
According to the documentation regarding the appPaused event:
iOS: A short amount of time is given for execution, it is generally best to assume that callbacks and timers may not fire until the app is resumed.
My application deals with websockets and ideally I am able to send a close event to my server when a user minimizes my app, or the phone is locked. Currently, all of my clean-up code runs perfectly on Android, but on iOS, my clean-up code doesn't run until the app is resumed. The strange part is sometimes (maybe 1 out of 20 times) the iOS clean-up is correctly run right after the appPaused event is fired.
To test this I have done two things:
I make the very first thing to run after the appPaused event is fired is a message to my websocket server saying "the app is paused". 95% of the time, this message is not actually sent until the app is resumed, but the other 5% of the time my websocket server receives it right after I pause the app.
I then made it so the first thing to run after the appPaused event is fired is a line that stores Date.now() in a global variable. I then then store Date.now() in another global variable when the app is resumed, and find the difference between them. It gets interesting because around 50% of the time the Date.now() line is correctly fired right after the appPaused event is called, but the other half of the time the 2 Date.now() calls are only milliseconds a part, proving that the clean-up code was not run until the app resumed.
So, can I really only expect to sometimes have enough time after appPaused is fired to even store Date.now() in a variable? Is this what everyone else is experiencing when running their Trigger.io applications on iOS? Let me know if anyone could use more information.
Yes, appPaused is not guaranteed to fire on iOS. Note, that it may only fire when the app is resumed.
Any code you want to run on going into the background will probably not run. See the trigger io docs on events. The note on iOS needs careful reading.
That said I have a similar app to yourself dealing in websockets. What i've found is that I can leave the socket open, iOS automatically kills the socket when it suspends the app (Sometime after the paused event is received which corresponds to the ios background state). The server receives the close event and you can of course attach to that event to set any state or cleanup resources. Android requires a manual shutdown of the socket, but appPaused works in a guaranteed way when the app is put into the background.

How does the Navita TEM app get call log information?

How does Navita https://itunes.apple.com/us/app/navita-t.e.m.-personal/id590228620?mt=8
manage to display a call log?
If I swipe the app out of the task manager then it misses the calls, this indicates that it must presumably be using CTCallCenter's callEventHandler and is creating its own call log by saving the time/duration in response to the callEventHandler callbacks.
However if that is the case then how does it manage to do this in the background? I was under the impression that callEventHandler can only be used by apps in the foreground and not in the background?
The app is using location services, however even after disabling this it was still able to get information about the calls (provided the app isn't suspended). I though it might be using background location updates to keep itself primed to receive callEventHandler callbacks but apparently not.
The Navita app is additionally able to display call time and call duration.
The bounty will be awarded to an answer which contains sufficient, accurate and detailed information that enables me to emulate the behavior of the Navita app, specifically I must be able to write an app that can obtain the time and duration of a phone call that occurred while the app was not in the foreground, while the device's location services was turned off, and after the app had been in the background longer than the ~3 minutes granted by using beginBackgroundTaskWithExpirationHandler:
Here is what I observe with the Navita app that I want to be able to reproduce:
1) Run app
2) Task away from app
3) Go to device settings, privacy, and turn off Location Services.
4) Go to device settings, privacy, background app refresh and turn off for the app
5) Wait > 10 minutes to make sure the app is not still in the background as a consequence of using beginBackgroundTaskWithExpirationHandler:
6) Call the device from another phone, answer the phone call, then hang up.
7) Launch the app again and display the call time and duration
(This is iOS7 and unjailbroken)
Here is what I've found from Navita TEM disassembly and it's resources.
Application uses two background modes - location and audio. You can see it in the Info.plist file. When you enable phone calls logging application will also enable "alerts" and "Real-time" switches. When "alerts" enabled application infinitely loops in background "bg-sound.mp3" file which has no sound, it's just silence. Because of that it doesn't use hacks like this one How to get a call event using CTCallCenter:setCallEventHandler: that occurred while the app was suspended? . It's similar trick to location used in order to keep the app running in background and receive phone call events. Somehow this was not rejected from the AppStore.
If you are observing the call center while the app is in the background you get a set of updates when the app is brought back to the foreground. This set of data may not be as accurate as if the app had been mostly in the foreground but it does include quite a bit of info.

performFetchWithCompletionHandler never gets fired

1) My plist configuration to provide backgroundmode:
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>
2) In didFinishLaunchingWithOptions I have:
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:1.0];
3) I declared the protocol UIApplicationDelegate in the delegate.
4) I implemented the following method, but it never gets fired. (It only works if I simulate the fetch with "XCode->Debug->Simulate Background Fetch".)
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
Why? Is this a DP5 beta error? Should I radar this?
Running your app in the iOS Simulator, in Xcode Debug mode, you can force a background fetch from the Xcode menu:
Debug > Simulate Background Fetch
May work for a tethered device, I haven't tried it recently.
I'm afraid this is hard to debug on a device because you're not guaranteed it is called in the amount of time you specify.
setMinimumBackgroundFetchInterval means that it is not called in an interval which is smaller than the value you specified. But there's no setMaximumBackgroundFetchInterval.
So if iOS decides to call your app just once a day or even just once a week than it won't be called more often regardless your minimumBackgroundFetchInterval. AFAIK iOS decides when to call performFetchWithCompletionHandler measured by the pattern when and how often the users start the app.
There are many considerations:
Make sure the background fetch capability was set in the plist.
Make sure the background fetch capability hasn't been disabled for this particular app, or in general, in the device's Settings app.
Make sure to set the minimum fetch interval.
Make sure you gracefully leave the app (e.g. just hit the home button and fire up another app and/or just lock the device). But if you kill the app (by “force quitting” by double tapping on the home button and swiping up or, for those devices without home button, swiping up from the bottom to pull up the task manager and then swiping up on the app in question) that will prevent the OS from offering your app a chance to fire off subsequent background fetch requests (at least until the user runs the app again).
Make sure you are testing this on physical device and not running the app via the Xcode debugger. Being attached to the debugger changes the behavior of background operations.
Make sure the app is actually doing some network requests. If you have app that performs no network requests at all, it won't participate in background fetch. If you do, for example, a little test app with "background fetch" and don't issue any network requests, you won't participate in background fetch.
Likewise, if the OS starts up your app in background mode so it can perform a background fetch, if you don't actually perform a network request, the OS may stop offering your app the ability to perform background fetches in the future.
Make sure to call the completion handler, and do so within the allotted time, or your app may not participate in background fetch in the future.
The timing of when the OS performs background fetch is dictated by poorly documented rules that may change in the future. But the relevant factors include:
Whether the device is connected to power and/or is sufficiently charged;
Whether connected to WiFi or not;
How often the user actually fires up the app;
Whether the device is doing other network related tasks (e.g. whether background fetch can be coalesced with other network operations);
How frequently past background fetch requests resulted in there being data available.
In my experience, after the app is run the first time, if connected to wifi and power, if you wake the device about 5 minutes later, the app will perform background fetch. This isn't a hard and fast rule, but just what we've experienced in the past.
But many new developers post on Stack Overflow with questions like “how can I have app request data ever x minutes (or hours)”, “how can I request data every day at 2 am time”, etc. The short answer is that you can't. The OS decides the timing of background at its own discretion. You cannot control this (other than the minimum request interval; but you cannot control the maximum interval, as the OS controls that).
This may seem obvious to many, but make sure you've got a reliable way of knowing whether background fetch process is running correctly or not. User Notifications framework can be used to present some alert so you know if the background request resulted in something. Alternatively, os_log or Logger “Unified Logging” (see WWDC 2016 Unified Logging and Activity Tracing or 2020’s Explore logging in Swift) can be used to post messages on device that can be monitored on macOS Console app. But more than once, I've seen users do something like waiting for message to show up in Xcode or waiting for UIAlertController. You need some mechanism that works when not connected to Xcode and when the app never enters foreground.
Using your device you can fire application:performFetchWithCompletionHandler with the following steps:
Put your app in the Background state
Lock your device and wait 5 minutes.
Unlock your device, this will fire the method
(It only works if I simulate the fetch with "Xcode->Debug->Simulate
Background Fetch".)
It's because you're in Debug mode. Please try launch app without Xcode.
Another thing to check is your plist file. Make sure the UIApplicationExitsOnSuspend key is not present.
Many people here on Stack Overflow have recommended using that setting as a way to force your app to start fresh each time it's launched. That does work, but the side effect is that it prevents the new iOS 7 background fetch feature from being triggered.
If application: performFetchWithCompletionHandler: never gets fired (unless you simulate it using Xcode), check also if "Background App Refresh" preference is "On" for your app. (Settings app -> General -> Background App Refresh)
Also, background fetch is disabled if the iPhone is in Low Power Mode.
Apple provides an algorithm which defines how often the background fetch should trigger, based on your own usage of the app. If you use it a lot, then it will fetch as often as possible, but if you use like at 4pm every day, the background fetch should trigger just before, so your data is updated when you launch it.

EAAccessory/EASession teardown during background

Using Apple's EADemo as a base I'm trying to understand backgrounding's influence on EAAccessories.
When I receive a connectNotification, I get the accessory, check the protocol and get the streams. With the streams I set the delegate, schedule on the RunLoop, and open
Currently when I go into the background I tear this all down. I back out everything I did and release all objects.
I also do this teardown on a didDisconnect.
Upon entry, I don't always get a didConnect so I walk the connectedAccessory list and check for appropriate devices.
-=-=-=-
The EA Demo sets up and tears down sessions on a view so we don't see whether there is background persistence.
The EA Demo code seems to suggest you can tear down and go back in as long as the device itself hasn't disconnected.
The session and the device however sometimes seem to get into a state when the iOS device has slept or put the app into the background and the device will stop acknowledging data or worse will stop pulling data out of the stream to the point that the stream fills up.
Question: When using backgrounding, should I not tear down a session/accessory that is active unless I actually get a didDisconnect notification. With this I assume that the Session and Accessory structures (and maybe the streams) will survive in the background?
My experience with the External Accessory framework is that it was poorly designed initially, in terms of robustness. Until iOS 4.0, trying to connect to multiple accessories would yield this error message:
Cannot use accessory. Only one accessory can be in use at a time.
Now it seems that there has not been much attention to what happens to accessories when apps go into the background. I have been assured that the correct behavior is for EASession instances to be released on accessory disconnect. But I don't think that an app going into the background qualifies as an accessory being disconnected. There isn't much to go by if you log the value of the connectedID for your accessories, you will notice that they do not change just for having switched to background and back to foreground. I suspect that the list of connected EAAccessory instances is not refreshed unless EAAccessoryManager is notified of a change (connect/disconnect) of an accessory.
I have been trying to teardown and recreate EASession instances and this has failed. I am now going to try leaving the EASession in place throughout the background/foreground transition. My one concern is that my app won't be receiving EAAccessoryDidDisconnectNotification and EAAccessoryDidConnectNotification notifications while in background.

Resources