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).
Related
How can I post data to the server when my app is be Terminated ? I use the NSURLSession class. I try to post data when applicationWillTerminate is running but it doesn't work.
applicationWillTerminate is not meant for such tasks and you may not have enough time to perform the actions related to server.
Refer this apple doc
This method lets your app know that it is about to be terminated and
purged from memory entirely. You should use this method to perform any
final clean-up tasks for your app, such as freeing shared resources,
saving user data, and invalidating timers. Your implementation of this
method has approximately five seconds to perform any tasks and return.
If the method does not return before time expires, the system may kill
the process altogether.
Use applicationDidEnterBackground method for this purpose.
Building on the other legitimate answers, you could try sending the data when the application enters the background, but in some cases the app may be terminated before the request completes. An alternative could be to store the data (depending on what your data consists of) locally (i.e. NSUserDefaults) and then make the request when the app is opened at some point in the future. An obvious caveat is that if the user is accessing the data from multiple clients like a website or another device, you will need to reconcile this when you make your request.
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.
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.
I’m working on a simple wrapper around CoreBluetooth to send any data to any device.
During developing I encountered a lot of bugs in framework, they were very annoying and to make my wrapper stable I had to shorten some of functionality for reliability.
For now I’m working on sending data from peripheral.
Ok, so I have following case:
Client asks for value of dynamic characteristic
I get a callback on server-side - peripheral:didReceiveReadRequest:.
Note : I need to respond to this CBATTRequest in this method - I can’t store it elsewhere and respond to it asynchronously. (Im just putting some chunk #“PrepareToReceiveValue” that will be ignored on central side. All sending is done in queue.)
For providing data for various devices I constructed a queue with BTMessage's in it. (So for readRequest I create message and add it to sending queue. If chunk sending failed - I will get a callback from peripheral manager about readyToUpdateSubscribers and will ask queue to resend failed chunk)
So when I’m requesting immediately a lot of dynamic characteristic values and sending data from peripheral to central concurrently sometimes it just freezes sending progress and leads to disconnection.
After several testing I found out that it was all about transmit queue:
If transmit queue is full and you will receive read request - it just won’t respond to it.
So I have potential unstable system state:
Peripheral is sending data to some central.
In my sending method updateValue:forCharac… returns NO because transmit queue is full.
At this moment central requests dynamic value for characteristic and peripheral:didReceiveReadRequest: invocation will be added to current runloop.
After returning from sending method it will dequeue peripheral:didReceiveReadRequest: method and responding to this request will have no effect (transmit queue is full).
So in this case respondToRequest: is ignored like I didn’t invoked it at all.
CoreBluetooth will not be able to send/receive any data until I will respond to request. That was the reason for freezing any sending/receiving progress with concomitant disconnection.
As I mentioned before - I must respond to request in appropriate method - otherwise it will also have no effect. (Im saying it because I’ve tried to put those request in array if queue is full and respond to them when it will have some space but with no luck).
Im waiting for your proposals/suggestions how to resolve this problem, any help would be appreciated.