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?
Related
App that I am working on is offering a VPN connection, that can run even when the app is not running at all. This service is paid, but also I would like to offer a free trial limited by session length and maximum data transfered.
The problem I've encoutered, is with monitoring the data trasnfered when the app is in background or not runing at all. So far the best solution I've came up with, would be to periodically run small task that checks if the user is still within the data limit and if not, the VPN will be disconnected and notification shown to the user.
Will silent notification get priority every time it will be required? According to this quote from developer.apple.com, they are low-priority which isn't what I need, but I was unable to find anything else.
Silent notifications are not meant as a way to keep your app awake in the background, nor are they meant for high priority updates. APNs treats silent notifications as low priority and may throttle their delivery altogether if the total number becomes excessive. The actual limits are dynamic and can change based on conditions, but try not to send more than a few notifications per hour.
How can this be done reliably? Is there any other way?
If this is a personal VPN connection (i.e. you're just providing a config to the standard system) and you're not in the flow, then this isn't possible. There is intentionally no "I want my program to run all the time" solution. Even if you come up with one, Apple will probably shut you down.
If you're writing an MDM/supervised VPN connection (i.e. you're providing a ...Flow object of your own), then you're already running all the time and you can just control it as you want. I'm assuming you have the former or you wouldn't be asking.
I believe you're doing this backwards. Monitor the session length on the server, and disconnect there. When you disconnect, send a push notification, which can display a message directly without having to open the app. That is both robust and the intended solution.
Periodically posting a silent notification to wake yourself up will definitely not work because Apple specifically does not want you to do that and they explicitly break it (as they note "silent notifications are not meant as a way to keep your app awake in the background"). It's bad for battery life. This is intended to be solved on your sever, on on the user's device.
I have been investigating iOS background fetch for our enterprise applications. According to articles like this, there are limitations like having 30 seconds to download before the app is terminated and the may be (unconfirmed) a penalty where after 3 timeouts, an app gets banned from background sync. Also if the user kills the app, fetches stop happening -noted here.
The goal is to be able to retrieve data from our servers periodically when app is suspended/not running but sometimes the transfers can take minutes due to long running SQL. I don't want to implement sending periodic notifications to all users.
Before I go down the path of developing for the iOS background sync, I needed to do some due diligence and research alternatives to iOS's background sync and didn't find anything.
Has anyone seen or developed an alternative to iOS's background sync or dealt with this issue for their enterprise apps?
As an enterprise app there's nothing extra you can do except that you can use whatever background modes you want (audio, location, voip etc,) without needing to have a legitimate reason to do so.
Where this might assist is:
you could make use of a significant location change (as opposed to a regular location change) notification to run your app in the background. The problem with this is it of course depends on the user of your app to move around. However, assuming everybody in your workforce commutes to/from work with their iPhone then you would have two opportunities each day for the app to run in the background. A app run due to a location change can be made to execute in the background for more than 30 seconds.
voip pushes: Unlike a regular push notification, a voip push will launch the app even if the user has force terminated it. To make use of this functionality is only a tiny bit more effort than using regular push, you don't have to do anything regarding making or receiving an actual voip call, you just need the voip capability and voip certificates instead of normal push certificates.
The comment in that link is not correct regarding force quitting and background fetch - a user force quitting an app does not make it ineligible to run for a background fetch, I have force quit my own app that uses background fetch but it will still be started by the OS, however what will happen is that the frequency when the app is run will decrease lots, and if the user never runs the app again then the OS will stop launching it.
A user force quitting an app will prevent other things from happening, such as it running in the background in response to a silent push notification for example (unless its a voip push).
Also the 30 seconds in not related to download times, NSURLConnection would stop after 30 seconds, NSURLSession is designed to continue to download on your app's behalf. But if you are downloading and then applying lengthy SQL processing it would be an issue. But minutes of processing time seems excessive, are you sure its all optimized?
The goal is to be able to retrieve data from our servers periodically when app is suspended/not running
The only reliable way to achieve such a behaviour is implementing a User-facing Remote (Push) Notifications framework on backend & apps.
You can append 4kB (or 5 for VOIP) worth of data in the push JSON payload, eliminating need for a network fetch request if implemented in a handshake mechanism.
You can evaluate usage on Silent Remote Notifications to augment content updation & fetch small amounts of content opportunistically, though it has the same as Background App Refresh.
You can definitely improve the API that can take minutes due to long running SQL
And remember you need to have the app updated only when the user actually fires it up. Evaluate implementing a catchy & smooth fetching content screen that transitions into the actual screens once all data is fetched.
I am not sure how to best implement keeping our server informed that our iPhone application is currently running (even when in the background). There are a few different options but there is some concern as to what is allowed by the Apple approval process as well as what is the most reliable. The application does have the ability to play music in the background, if that factors into what is approved by Apple.
The first option, is to continually send some sort of heartbeat to the server at a set interval through a simple GET/POST; however, the concern is whether or not this is allowed as a background task. In a very roundabout way it can be argued that it is necessary for the playback but I'm not so sure whether or not that is acceptable. If the server does not hear from them in a set amount of time it will assume the app is no longer running.
The second option involves using a presence channel socket connection and have the server just handle when users enter and leave that channel. With this option the main concern is how reliable is a socket connection like this while an app is in the background. Similarly, whether or not this is allowed by Apple. In this case when the app dies, connection closes and server knows app is not running.
Third option can be used in tandem with either of the other options but to use some sort of APNS push to query the phone as to whether or not it has died and have it respond with some data to let us know; however, this seems somewhat counterintuitive as the push itself wakes the app up.
Any other suggestions or input are also welcome.
Not sure if this should be a comment or answer, but let me put my 2 cents here.
Firstly, Can you please elaborate your needs further, because in case you are playing an Audio in background with AVPlayer/AVPlayerItem you would hand over your content URL to iOS and it will make the calls as and when necessary to keep the playback running, you dont need to know about apps' state.
Let me assume, for whatever reasons you want to achieve what the question asks:
There are 3 states your app can be in when it is "Not Running"
i. Suspended State: your app is not killed but its not receiving any CPU time to execute your code.
ii. Killed by OS: Your app can be terminated by iOS to free up the memory or any other resources.
iii. Force Killed by User: If user swipes up your app from app switcher it gets force killed.
Now when your app is Not Running, you CAN NOT query it, but you can move it to Running State. You can achieve this transition by using following methods (Not exhaustive list, but mentions common ways)
i. Background Fetch : You can configure your app to be invoked periodically, so that it can synchronise with the server and updates its state.
ii. Push Notifications (APNS) : You can ping the app from server so that iOS invokes it for some short period of time (few seconds) to update its state.
iii. VOIP Pushes: If your App is VOIP app you can use PushKit to send Silent Pushes which will launch even the Force-Killed Apps, the above two methods does not transition the app to Running state if it was force killed by user
The above point can be helpful in devising overall strategy but does not answer the question, "How to keep syncing the RUNNING state"
i. When your app is Running(Forground/Background), you can do almost anything that is publicly documented, you can keep calling a URL every minute or every 5 seconds, you need to worry about UX on the device rather than approval process, (People will delete app if they see your app in top battery drainers in the settings section)
ii. For making an HTTP call while your app is in background, you can look at Background URL Session, which off loads the HTTP calls to another process so that the call completes even if app gets killed.
iii. For the socket based approach please refer this technical note. The main issue is that you do not/can not know when your app moves from Running to Not Running State.
iv. As you mentioned that your app uses background audio, it will be always be in Running state if the user plays an audio and puts app in background, in such case you can use Periodic Observer to do some Heartbeat call periodically when the content is being played out.
My iOS app (which targets iOS 8.1+) use location services to determine if a user has entered a particular region during an event. Ideally I would like to enable the geofencing a little before the event and turn it off a little after the event completes. The problem is there is no guarantee the app is running an hour before the event so I turn on geofencing at the point the user registers for the event. This is not the best approach as it means the geofencing is on for much longer than it needs to be.
As far as I can tell, there is no way to "wake up" the app in the background at a scheduled time in iOS. I could use the push notification meant for updating content, but It's not clear to me if Apple would reject such a misuse of that notification.
Any suggestions?
I've been looking into this myself.
If you can arrange a push notification from your backend ("Silent push notification", aka "content-available"), it seems to be a good option. In real-world situations, this seems to give you the best control over the timing. Unfortunately, it won't work if the person doesn't have connectivity. Also, unfortunately, you need a backend that can queue events at a time (not just in response to an input.) If you have such a backend already, and your app is only useful to the user when they have network coverage anyway, this is probably the best option. It seems this an appropriate use of the technology, so Apple should approve.
Another option I'm trying is to use background fetch. You specify a "minimum interval" to avoid too much fetching. Try 50% of the remaining time-to-event as a minimum. Every time the app wakes up (whether in the foreground because the user opened it, or in the background because background-fetch opens it) you can calculate the time-to-next-event, update the fetch interval, or start the region monitoring. You are supposed to use "background fetch" to fetch information from a server, but there doesn't seem to be any requirement to poll a server, you could poll your internal data instead. I haven't fully tested this yet but it seems promising.
You can use significant location change monitoring, which I've read will wake up your app briefly every 15 minutes or less, and you can use the time/location information to decide whether to turn on the geofence. I think this would work well in combination with the above "background fetch": Many hours or days before the event you rely on background fetch, which you then use to turn on significant location change monitoring a few hours before the event. (There's speculation that geofencing is actually more battery efficient than significant change monitoring, but you could choose to assume that other apps on the user's device will already by watching for significant changes, in which case the marginal cost of your app adding itself to the list should be minimal.)
Putting them all together, you could create a sequence of
background fetch -> significant location monitoring -> geofencing
as the time gets closer.
There is also the CLVisit monitoring functionality, it's not understood very well, but supposedly uses less power and is called less frequently than significant location change monitoring. If the background fetch or silent remote notifications aren't working to wake up your app, give this a try, and please report back!
You can't (yet) do a silent content-available local notification (AFAICT). However, perhaps you can schedule up a local notification "Your event starts tomorrow" or something that convinces the user to click the option that starts the geofence. Here's a tutorial on it http://www.appcoda.com/local-notifications-ios8/, the response of action can be UIUserNotificationActivationMode.Background so your geofence can come on (if the user responds to the notification) without bringing the app to the foreground.
It's been 5 to 6 weeks since you asked, do you have your own answer already? Please let me know.
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.