I use HKObserverQuery and background delivery in my iOS application. In the updateHandler of HKObserverQuery I execute a query (HKAnchoredObjectQuery) and send results to the remote server via https. But on a slow connection (EDGE for example) and with big amount of data (steps for example) data sending may take up to one minute.
From the documentation to HKObserverQueryCompletionHandler:
When HealthKit wakes your app, it calls the update handler on any observer queries that match the new data. 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 back off algorithm. If your app fails to respond three times, HealthKit assumes that your app cannot receive data, and stops sending you background updates.
So my question is: How much time do I have to call HKObserverQueryCompletionHandler before my app is assumed by HealthKit as "hanged"?
Or should I call HKObserverQueryCompletionHandler immediately and run a new long-running background task for https request instead?
You should definitely start a separate background task to perform the transfer of data to your server and call the HKObserverQueryCompletionHandler as soon as possible. The documentation gives no indication as how long you can wait before calling the handler so the safest thing to do is call it as soon as possible.
I posted a similar question about confusion around use of HKObserverQueryCompletionHandler here, but there haven't been any updates.
Related
I am a bit confused with how performFetchWithcompletionHandler engaged with the iOS app life cycle. In the docs it's mentioned that it gives us 30 seconds to download any data but will our app freeze for 30 seconds at that time?
... will our app freeze for 30 seconds at that time?
No, the exact opposite. This method is called when your app is suspended, but the OS is going to let you check for data to retrieve. So, your app will be awaken, run in background, and this method will be called, at which point you can perform your quick fetch (not more than 30 seconds) to see if there is any data to retrieve. When you’re done processing your quick fetch, you call the completion handler to let the OS know that you’re done and that your app can safely be suspended again.
If, though, you fail to complete your request in 30 seconds, your app may be summarily terminated and not participate in future background fetches. So it’s important to finish in the allotted time.
As the docs say:
Implement this method if your app supports the fetch background mode. When an opportunity arises to download data, the system calls this method to give your app a chance to download any data it needs. Your implementation of this method should download the data, prepare that data for use, and call the block in the completionHandler parameter.
When this method is called, your app has up to 30 seconds of wall-clock time to perform the download operation and call the specified completion handler block. In practice, your app should call the completion handler block as soon as possible after downloading the needed data. If you do not call the completion handler in time, your app is terminated. More importantly, the system uses the elapsed time to calculate power usage and data costs for your app’s background downloads. If your app takes a long time to call the completion handler, it may be given fewer future opportunities to fetch data in the future. For more information about supporting background fetch operations, see Background Execution in App Programming Guide for iOS.
This is regarding background fetch, like when an app receive silent notifications. In this case, iOS wakes up an application in background for max 30 sec. and do the stuff as per writer code and the kill the app. So user will be unaware of this.
It is not recommended to write complex logic in this method performFetchWithcompletionHandler. The reason is the time limit(30 sec.) and the app developer doesn't have a control over it.
To get this work, background mode should be enabled in capabilities of project.
One of the example about this is silent push notification (notification payload has the key content-available = 1)
For more details see this
I'm trying to make an app that uses HealthKit weight data and sends it to a remote server. Should I put the completionHandler in the "success" hook of the async request to the server or should completionHandler always be called if error is nil (this seems to be how most examples are written).
The completionHandler should always be called. If you don't call it, including in the case of errors, then Apple will eventually stop calling your query in the background. Be advised, HealthKit data is encrypted when the device's screen is of and if it has a pass-code set. While it is encrypted the data is unreadable, even though the HKObserverQuery will still notify you that something has changed (you just can't tell what was changed). The encryption makes background processing of HealthKit data almost impossible unless the user disables their passcode/touchid (which obviously is not advisable).
We're working on an enterprise app that sends form data entered by the user to the server. The app attempts to send the data immediately after the user taps Save. If there is a problem sending the data (e.g. no network connection), we want to try again X minutes later, and periodically retry until the data is successfully sent. The requests are typically not very large or long-running, but the user is frequently in environments without network access, so retries are important.
It's simple enough to retry API requests when the app is running, but ideally we want to keep periodically retrying when the app is in the background. What is the best that we can achieve with iOS background execution features? Here are some options I see:
Use beginBackgroundTaskWithExpirationHandler: to make a one-time request for additional time. This would give us basically one additional attempt to send the data after the app goes to the background, but not repeated attempts
Use the "background fetch" feature, and attempt to send any new data to the server when the app is woken for background fetching. Sounds good in theory, but I'm concerned this may be an abuse of the background fetch feature?
Is one of these my best option? Is there another approach I'm missing?
This is a Xamarin app with iOS and Android implementations, though I don't think that has a significant impact on my question; I can write iOS specific code as needed.
I think there is one approach that you missed : background NSURLSessions. And I think this is what you should use.
I don't really know how many shots you can have with this. Here is what I recommend :
Begin a background task as well (with beginBackgroundTaskWithExpirationHandler:) when you start your background download.
As long as your background task hasn't expired, you will be able to restart the download, even if in the background.
If and when your background task expiration handler is called you need to end the background task immediately. At this point your app will stop executing and your download will continue in an external process.
When the download completes or fails, your app will be restarted in the background and given a chance to handle the result. The thing I don't know is if you will be able to start a new background download at this moment. You should just try or wait for someone that knows better than me...
Also be sure to check the -(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes; delegate method in order to restart your download where it failed and not start over.
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.
iOS 7+, 8 (last one not released yet, however targeting on it).
The app.
As a user I start the app and switch to other apps (mail, safari, etc.), leaving the app running but not a foreground one.
The app establishes HTTP connection to server via Internet and starts periodically sending GPS location data to the server (with some interval).
Is it possible while the app is not on the foreground? I mean is it possible to get geolocation data and periodically send it from the app to the server via HTTP POST while using other apps?
If the answer is "YES", please help me with references. I will investigate it further.
Yes, and the method you want to research is performFetchWithCompletionHandler:
Implement this method if your app supports the fetch background mode.
When an opportunity arises to download data, the system calls this
method to give your app a chance to download any data it needs. Your
implementation of this method should download the data, prepare that
data for use, and call the block in the completionHandler parameter.
When this method is called, your app has up to 30 seconds of
wall-clock time to perform the download operation and call the
specified completion handler block. In practice, your app should call
the completion handler block as soon as possible after downloading the
needed data. If you do not call the completion handler in time, your
app is terminated. More importantly, the system uses the elapsed time
to calculate power usage and data costs for your app’s background
downloads. If your app takes a long time to call the completion
handler, it may be given fewer future opportunities to fetch data in
the future. For more information about supporting background fetch
operations, see “App States and Multitasking” in iOS App Programming
Guide.
https://developer.apple.com/library/ios/documentation/uikit/reference/uiapplicationdelegate_protocol/Reference/Reference.html#//apple_ref/occ/intfm/UIApplicationDelegate/application:performFetchWithCompletionHandler:
Just as Mike mentioned, you should look into background fetch. For more details, checkout this objc.io post.
The information you need should be in the Background Fetch section.