Problem: I need to remain disconnected from a BLE peripheral but send data it's data to a server for processing as often as possible, as it is potentially time-critical. In other words, I want to connect every so often and send the synced data to an API, while remaining disconnected at all other times to save battery life.
Failed Attempt: Setting the UIBackgroundModes field of my app's Info.plist file to bluetooth-central only gives me background execution while I am connected. I want to remain disconnected, but reconnect at predefined intervals, as well as schedule an alarm from background mode.
It's possible: I've noticed that the FitBit Flex app has an option in the settings to enable syncing in the background. I am not sure if it ever disconnects from my Flex while it is in range, but judging its so-small-I'd-lose-it battery size, I'm guessing it does not remain connected.
I know I've already accepted an answer for this (sorry!), but I've found a solution:
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:60*5]; // Every 5 minutes, minimum
in:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
and adding fetch to UIBackgroundModes, which iOS then calls:
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
every 15-240 minutes (yeah, it varies a lot, but it's better than nothing). Every time I am called to perform a fetch, I connect to the peripheral, sync and send its data to the server, then disconnect. Since I'm sending this data from the BLE peripheral to a server for processing/storage, I'm presuming that this is a legitimate (AppStore worthy) use of fetch.
CAVEAT: application:performFetchWithCompletionHandler: will not be called until iOS establishes a user usage pattern for the app. In other words, you need to keep the app around (not delete it) for about 24 hours or so before the application:performFetch... method gets called. Boy, did it take a while to figure that out!
UPDATE: Apple has accepted my app that used this solution (approved May 2014).
You can't. A backgrounded app cannot start any transactions, it can only react to incoming requests (data notifications, connection events...) In the background the application is effectively not running and even in case of BLE events it has only 8 seconds to go back to sleep, otherwise it is completely terminated for breaking the policy.
The only case when your app can stay alive for a while is when it uses the beginBackgroundTaskWithName:expirationHandler: API. But even in that case it has only 10 minutes to complete or it gets killed.
If you want to synchronize while in background, then the transaction must be started on the external peripheral's side. Easiest is to stay connected and send a notification to the central every once in a while. That will wake it up and it can proceed with reading the characteristics it needs. But there are several other ways to implement it. The final solution has to be designed to best meet your needs. If you have a concrete idea, then please submit it as a separate question.
https://developer.apple.com/hardwaredrivers/BluetoothDesignGuidelines.pdf
I'm pretty sure you guys have seen this, but just in case you haven't. Perhaps on the BLE Peripheral end you can save power by increasing the connection interval (page 22).
Related
Preamble
I wrote a mobile application which should show upcoming events. The app downloads it's data from server. Data is prepared in batches once every 24 hours and it's ready to be fetched after 4 am. This gives me a perfect opportunity to sync it overnight and to make new data immediately available when the user opens the app.
Background fetch
This was my first approach for syncing data with the server. It's advertised as very powerful feature, but using it alone (out of the test environment) is not enough:
There is no way to force background fetches to execute at exact intervals.
I thought that the frequency of background fetch operations could be configured
// Setup background fetch
let timeIntervalEveryHour: NSTimeInterval = 3600
let sharedApp = UIApplication.sharedApplication()
sharedApp.setMinimumBackgroundFetchInterval(timeIntervalEveryHour)
but it's still dynamic and I think it is never for users who did't use app very often.
If 'Background App Refresh' is disabled, which automatically happen if device is in 'Low power mode', background fetch won't be triggered.
Other problems like Data Protection when device is locked and 30 seconds window for completion of all tasks are considered.
Remote (silent) notifications
So I took the next step and configured the server to post silent notifications once the batch is ready just to found that this is also not enough:
If application is force killed by the user or device is rebooted, notification won't be handled.
Rate limits. Delivery will be delayed, this depends on a variety of factors that are not explicitly specified by Apple, but probably - battery life, whether phone is on cellular, etc.
Sometimes silent push notifications are dispatched when application starts, which could lead to race conditions with the check for manual synchronisation. So I'll try to force it by adding "alert" = ""; to payload. (As it is suggested here)
Silent Push notifications could be disabled by the user be setting off 'Background App Refresh' - source
Manual synchronisation
To be sure that data is always up to date, if it's not recently updated, when app comes to foreground user is presented with alert which asks for manual synchronisation. Also it could be started later from settings tab. Unfortunately, according to analytics, most of the fetch requests are made manually.
First run case is also handled.
Next steps
I'm considering using VOIP notification. They should wake the app even if it's force killed. However I'm concerned that that would cause app get rejected.
Questions
Is there something I'm missing? I know that background synchronisation depends on a variety of factors, there could be no internet connection and etc, but is there any way to make it more reliable?
I am a little bit confused about this question.
Somewhere I read, it is not allowed by Apple to make network requests, when an app not in the foreground. Is also the case, when app is woken up by locationManager events?
What I'd like to: define a Beacon region, handle locationManager:didEnterRegion: and locationManager:didExitRegion: to call my simple web service methods. It should be done even if the app is not active and woken up by location manager.
Technically it is easy to implement - basically, I have already made it - but I don't know if it is allowed by Apple, and would take app review successfully.
Thanks!
Yes, it is allowed. It is a common technique, and I have several apps in the AppStore that do this. The Beacon Scavenger Hunt, for example, sends stats back to our server when participants use the iOS app to find beacon targets in the hunt.
When you detect a beacon in the background, however, you only have about 5 seconds of running time from iOS. Take care that your server responds quickly enough. This small time window is the way Apple enforces that people don't abuse this.
With iOS7, it is possible to have completionHandler for Remote Notifications. Is it possible to do the same for UILocalNotifications?
Basically, I want a webservice to post my some data at regular time intervals of 30 seconds, even if the app is in background. For this I considered 3 options but didn't get help from any :
Background Fetch : This will work in background, but I can't use it as it is not mandatory that iOS will always invoke this background fetch at my desired time intervals.
Remote Notifications : This works perfectly. But every 30 seconds I have to post a Remote PUSH Notification, which is not at all practical. Also it'll be great if I could handle it locally.
UILocalNotifications : There's no completion handler for this. User WILL HAVE TO open the app. So this ain't working as well!
Are there any other options? Or even with iOS7, it's still not possible to do something locally in background?
Please help. Thanks!
You covered all the options, and as you see, this isn't supported. For good reasons, mostly, as waking up the application in the background is a costly operation (in iOS7, a snapshot is taken after apps finish their background work), doing it every 30 seconds would be devastating to the battery life.
Seeing as you haven't mentioned what data you need to post, in most cases I would suggest you redesign your app to be friendly to your users' batteries. If you need location reporting, consider listening to significant location changes instead of recording every 30 seconds.
Abusing push notifications is possible (note, that silent remote notifications are rate-limited by Apple), but consider the experience your users will have.
If you feel that this feature should be there and is missing, you should open an enhancement request with Apple and post the radar number here so people can duplicate it.
Background fetch is a direct answer for your problem. Background fetch initiates your fetch handler whenever the iOS is free to execute a task periodically. All you need to do is initiate you NSURLSession request in
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
NSURLConnection is no longer a valid API for service calls or nor it supports background tasking. NSURLSession is clearly a replacement API for NSURLConnection with its advanced iOS 7 benefits. Adding below, documentation from Apple's iOS documentation
NSURLSession is a replacement API for NSURLConnection. It provides
options that affect the policy of, and various aspects of the
mechanism by which NSURLRequest objects are retrieved from the
network.
Background fetch interval can be set to define the repetition frequency for background fetch, but it also considers factors of the OS resources and pending operations
- (void)setMinimumBackgroundFetchInterval:(NSTimeInterval)minimumBackgroundFetchInterval;
iOS 7 clearly gives a better way to update content of the application, but it respects device resources & user priority as iOS generally does. Hope this answers your question.
Actually, all looks like you can't communicate with webservice at regular time intervals from background.
UILocalNotifications or Remote Notifications need user's action to wake up the app if it's backgrounded. EDITED: Starting from iOS 7.0 remote notification can wake up the app but it's not a flexible solution
iOS allows more activities in background for applications that use one of specified UIBackgroundModes, see please "Table 3-4 Background modes for apps":
Here is a link to related Apple docs
If your application isn't positioned for one of bg modes directly, I agree with Anil Kumar (post above) that background fetch is the most useful thing here. However it doesn't do completely what you need. [UIApplication setMinimumBackgroundFetchInterval:] it doesn't mean any strict time. It means minimum interval only (or desired time). Here is more info:
performFetchWithCompletionHandler never gets fired
Thanks
I want my app to periodically wake and check a status, but burn minimal cycles between wake ups. Is the a Core call for this or an accepted design pattern?
Prior to ios7, apps could only ask for execution cycles in background for a handful of purposes (e.g. playing sound or getting location updates). While they added more options in ios7, like periodically fetching data from a server, I'm still not clear there is a way to do this. (It's also a bit difficult for me to imagine why your app cares about battery if it isn't running, but maybe that is my failure of imagination.) Recommend reading Background Execution and Multitasking in the AppleDocs.
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.