So according to Apple, I need to call the HKObserverQueryCompletionHandler after the updateHandler is triggered from data being added to HK.
But as soon as I call the completionHandler, the observer query stops giving any more updates in the background..
Here is my code:
guard let sampleType = sample as? HKQuantityType else { return nil }
let query = HKObserverQuery(sampleType: sampleType, predicate: nil, updateHandler: { query, completionHandler, error in
completionHandler()
IamExecutingHKStatisticsCollectionQueryHere()
})
healthStore?.execute(query)
healthStore?.enableBackgroundDelivery(for: sampleType, frequency: .hourly, withCompletion: { success, error in
})
If I don't call the completionHandler, everything works fine but I have never tested for long periods of time..
Calling completionHandler() indicates that you are done processing new data. Only call it once you have processed the results of the queries you execute in response to updateHandler being called. If you call completionHandler() early as you are now, the system will stop running your app in the background before you have a chance to process data.
Related
For my App on Apple Watch I want to observe when a workout in Workout-App was completed, to do a background update of my App and re-render the Complication. But the Query never fires.
My Approach was to use an HKObserverQuery and observe .workoutType(). Documentation mentions:
HealthKit uses workout types to create samples that store information about individual workouts.
On https://developer.apple.com/documentation/healthkit/hkobjecttype/1615132-workouttype. So that felt like the right sampleType.
I added com.apple.developer.healthkit.background-delivery to the Entitlements of the WatchApp Extension and Workout access permissions are setup properly.
That is the code, I am using:
let query = HKObserverQuery(sampleType: .workoutType(), predicate: nil, updateHandler: { [weak self] _, completionHandler, error in
guard error == nil else {
// CoreInsights.logs.track("HK Query Error", level: .error)
return
}
// CoreInsights.logs.track("Update Trigger HK", level: .info)
// self?.performBackgroundTasks(completion: {
// completionHandler()
// })
})
self?.healthStore.execute(query)
self?.healthStore.enableBackgroundDelivery(for: .workoutType(), frequency: .immediate, withCompletion: { updateSuccess, _ in
// CoreInsights.logs.track("HK Background \(updateSuccess)", level: .info)
})
healthStore.enableBackgroundDelivery returns with success true.
Someone knows, if I am using the right type and setup background correctly?
I need my app to sync between HealthKit and our database while it's in the background. I just can't wrap my head around the logic that determines how and when the HKObserverQueries run their updateHandlers. I need data of various different sample types, so I assume I need an observer query for each one. Right?
According to Apple about function enableBackgroundDeliveryForType, "HealthKit wakes your app whenever new samples of the specified type are saved to the store." But if I enable background deliveries and execute observerqueries for, say, blood glucose and weight, they both seem to run their updatehandlers whenever I input data in either one of them in the Health app. This also seems to happen even when I enable background delivery for only one of the sample types. Why?
func startObserving(completion: ((success: Bool) -> Void)!) {
let sampleTypeBloodGlucose = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBloodGlucose)!
let sampleTypeWeight = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass)!
// Enable background delivery for blood glucose
self.healthKitStore.enableBackgroundDeliveryForType(sampleTypeBloodGlucose, frequency: .Immediate) {
(success, error) in
if error != nil {
abort()
}
}
// Enable background delivery for weight
self.healthKitStore.enableBackgroundDeliveryForType(sampleTypeWeight, frequency: .Immediate) {
(success, error) in
if error != nil {
abort()
}
}
// Define update handlers for background deliveries
let updateHandlerBloodGlucose: (HKObserverQuery, HKObserverQueryCompletionHandler, NSError?) -> Void = {
query, completionHandler, error in
if error != nil {
abort()
}
// Handle data and call the completionHandler
completionHandler()
}
let updateHandlerWeight: (HKObserverQuery, HKObserverQueryCompletionHandler, NSError?) -> Void = {
query, completionHandler, error in
if error != nil {
abort()
}
// Handle data and call the completionHandler
completionHandler()
}
let observerQueryBloodGlucose = HKObserverQuery(sampleType: sampleTypeBloodGlucose, predicate: nil, updateHandler: updateHandlerBloodGlucose)
healthKitStore.executeQuery(observerQueryBloodGlucose)
let observerQueryWeight = HKObserverQuery(sampleType: sampleTypeWeight, predicate: nil, updateHandler: updateHandlerWeight)
healthKitStore.executeQuery(observerQueryWeight)
completion(success: true)
}
If you are using the background delivery feature of HealthKit, then yes, you do need to open an HKObserverQuery for each type of data you observe and handle the invocations of the updateHandler and call the provided completion when you are finished. However, the updateHandler of HKObserverQuery is advisory and invocations do not necessarily correspond one-to-one with changes to the HealthKit database (there is not always enough information available to determine what your app has processed and what it hasn't, so sometimes the handler may run when there isn't new data).
Don't worry about understanding or controlling precisely when updateHandler runs - just use it as a trigger to perform other queries that will actually give you up-to-date values from HealthKit. If you need to know precisely which samples in HealthKit are new, for example, then your app should use an HKAnchoredObjectQuery.
I'm trying to make my app synchronize HealthKit data with our database whenever new data input is observed. Despite some ambiguity in the logic I believe I've managed to enable background delivery for the sample types and have the observers react when needed.
However, in the observer's updatehandler I need to create an HKAnchoredObjectQuery for fetching the most recent results from HealthKit, but these queries don't return reliably when my app is running in the background. Often when I add sample points in HealthKit, the anchored queries just get executed, but they return only when I bring the app back in the foreground.
Other times they return immediately without me having to activate the app. One possible issue is that all of my observer queries fire their updatehandlers, thus creating multiple AnchoredObjectQueries that may or may not return. I've explained that further in the linked thread, because it may be unrelated.
Here's an example of the function I'm calling from HKObserverQuery's updatehandler:
func synchronizeRecentData(sampleType: HKSampleType, observerQuery: HKObserverQuery) {
let completionHandler: (HKAnchoredObjectQuery, [HKSample]?, Int, NSError?) -> Void = {
[unowned self] query, results, newAnchor, error in
if error != nil {
abort()
}
// Update queryAnchor
self.queryAnchors[sampleType] = newAnchor
guard let receivedNewSamples = results as? [HKQuantitySample] else {
abort()
}
// Handle received samples here
}
let query = HKAnchoredObjectQuery(type: sampleType,
predicate: nil,
anchor: self.queryAnchors[sampleType]!,
limit: HKObjectQueryNoLimit,
completionHandler: completionHandler)
healthKitStore.executeQuery(query)
}
According to the HealthKit docs:
Unlike the observer query, these updates include a list of items that
have been added or removed; however, anchored object queries cannot be
registered for background delivery. For more information, see
HKAnchoredObjectQuery.
The only query that can register for background delivery is the HKObserverQuery
I believe this is because when your app gets woken up in the background you have very limited time to execute before the app gets suspended again. If you use UIBackgroundTaskIdentifier you should be able to ensure that your app runs long enough to finish getting the results of the HKAnchoredObjectQuery.
like the code below, when I want to return a pedometer data through a method for some connivence, but the method returned earlier than the data be retrieved.I think this maybe a concurrency issue. How could I return the data in a right way for future use?Thx
func queryPedometerTodayTotalData() -> Int {
var pedometerDataOfToday: CMPedometerData?
self.queryPedometerDataFromDate(NSDate.today()!, toDate: NSDate(), withHandler: { (pedometerData, error) in
pedometerDataOfToday = pedometerData!
print("this print after got data in a background thread:\(pedometerDataOfToday)")
})
print("This should print before last print, and there haven't got the data now: \(pedometerDataOfToday)")
return pedometerDataOfToday
}
You're right about it being a concurrency issue. You should use the result inside the handler of the queryPedometerDataFromDate.
One way of achieving this would be to use a completion block for your queryPedometerTodayTotalData method instead of having it return a value, like this:
func queryPedometerTodayTotalData(completion:((CMPedometerData?)->())) {
var pedometerDataOfToday: CMPedometerData?
self.queryPedometerDataFromDate(NSDate.today()!, toDate: NSDate(), withHandler: { (pedometerData, error) in
pedometerDataOfToday = pedometerData!
completion(pedometerData)
})
}
func testQueryPedometerTodayTotalData() {
self.queryPedometerTodayTotalData { (data) in
print(data)
}
}
It is a concurrency issue. queryPedometerDataFromDate is an asynchronous method. it executes the completion block whenever iOS deems it (which would usually be after it has retrieved the data) so that is why the 2nd print line prints first and doesnt return your result.
You need to either use the pedometerData from the completion block inside the completion block, or call a method/delegate that will handle the result of the completion block.
I have a http request, and if I receive the response correctly, then I want to start a timer that fires a function each second. That function is also a http request.
this is my code to fire the timer
if let data = data {
do{
let resultJSON = try NSJSONSerialization.JSONObjectWithData(data, options: [])
requestClient.id = resultJSON["id"] as! Double
self.timerResponse = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "checkIfThereAreResponeses", userInfo: nil, repeats: true)
self.timerResponse!.fire()
}catch{
}
}
as you see, I'm calling a function called checkIfThereAreResponses
In that function, i have a print statement, and that print statement is being printed just once, though my timer supposed to work each 1 second
what missing i have?
And this is the function
func checkIfThereAreResponeses(){
if (requestClient!.id != nil) {
let url = NSURL(string: "http://localhost:blablabla")
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
let session = NSURLSession.sharedSession()
task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let error = error {
print("error = \(error)")
}
if let data = data {
do {
let resultJSONArray = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! NSArray
bla bla bla
}catch {
print("no responses yet = \(error)")
}
}
})
task!.resume()
}else {
print("not yet ")
}
}
the print that i receive JUST ONCE is the no response yet
If you have this code in completion handler, it is recommended NSTimer is running in the main thread so this could be the reason. You might need something like:
dispatch_async(dispatch_get_main_queue(), {
// Your timer logic here
})
Apple docs say:
Timers work in conjunction with run loops. To use a timer effectively,
you should be aware of how run loops operate—see NSRunLoop.
(NSTimer)
The NSRunLoop class is generally not considered to be thread-safe and
its methods should only be called within the context of the current
thread. You should never try to call the methods of an NSRunLoop
object running in a different thread, as doing so might cause
unexpected results.
(NSRunLoop)
An NSTimer works by being scheduled on a runloop. If you call this method in the completionHandler of the session, it will be scheduled to the runloop of the thread that that completionHandler is currently running on. As this thread is not owned by you, but by the system, it will be disposed of by the system. This might be immediately after the completionHandler is done executing, or it might be much later. With the thread, the runloop is gone too, thus, your timer might fire never, a couple of times or just once; there is no telling really as it depends on when the system will remove the thread.
Therefore, you should create the timer on a thread you own, or, easier, dispatch the creation of the timer to the main thread (dispatch_get_main_queue() is your friend). Another option is to create an NSTimer with one of the +timerWithTimeInterval... methods and then add it to the main runloop using [NSRunloop mainRunloop] addTimer: yourTimer forMode NSRunLoopCommonModes]. Remember that the timer will call your selector on the same thread as the the runloop it runs on.
As a side note, polling a server might not be the best way to go on a mobile device. If you control the server, it might be better to send a push notification to the device when there is new data for it. This will save battery on the device, and reduce the load on your server. However, if you do not control the server, it would be more complicated to achieve this, and then polling might be a good compromise.