Better way to run multiple HealthKit sample queries?
Asked Answered
S

3

6

I have a scenario where I need to retrieve multiple sets of data from HealthKit -- body temperature, weight, and blood pressure. I need all 3 before I can continue processing because they're going to end up in a PDF.

My naive first approach is going to be run one, then in the HKSampleQuery's resultsHandler call the second, then in that resultsHandler call the third. That feels kind of -- I don't know -- it feels like I'm missing something.

Is there a better way or is the naive approach reasonable?

Stoneware answered 24/4, 2015 at 18:32 Comment(0)
S
1

You should try to run the queries in parallel for better performance. In the completion handler for each one, call a common function that notes a query has completed. In that common function, when you determine that all of the queries have finished then you can proceed to the next step.

One simple approach to tracking the completion of the queries in the common function is to use a counter, either counting up from zero to the number of queries, or down from the number of total queries to zero.

Since HealthKit query handlers are called on an anonymous background dispatch queue, make sure you synchronize access to your counter, either by protecting it with a lock or by modifying the counter on a serial dispatch queue that you control, such as the main queue.

Sherrer answered 26/4, 2015 at 5:12 Comment(2)
Just a side note on the suggested approach of tracking the completion of the queries using a counter: You should pay attention to the fact that healthKit queries are asynchronous which mean you should protect the counter when changing it.Chanellechaney
@Chanellechaney Good point, I'm going to update the answer to include that.Sherrer
G
7

I ran into this same problem, and a much better approach for any kind of nested async call would be to use GCD's dispatch groups. These allow you to wait until multiple async tasks have completed.

Here's a link with an example: Using dispatch groups to wait for multiple web services

Guthrie answered 3/2, 2016 at 0:35 Comment(3)
Comment about the link: Isn't it possible for the first web service call to return before the second web service call is added to the dispatch group? If so, the dispatch_group_notify() will get called before the second web service request is finished. Would a better solution be to call dispatch_group_enter(serviceGroup); back-to-back before making any web service call?Wishbone
@Wishbone as long as the calls dispatch_group_enter are made before notify, you're safe. If the first one finishes before we get to notify, then notify will just wait for the second. If they're both finished, notify will just fire it's block. GCD dispatch groups are an extreme abstraction of multi-threading so they make life easier but you still have to be careful. If you can't call your enters and notifies in order, then you can dig a little deeper into GCD to get more control.Lelahleland
@Wishbone and to clarify by what I mean by "If you can't call your enters and notifies in order" is they need to happen synchronously on the same thread. If you make a call to enter on an async thread that gets executed after notify, then the notify callback will be executed unexpectedly. There is a good discussion here GCD group discussionLelahleland
R
6

You're going to want to use GCD dispatch groups.

First, set up a global variable for the main thread

var GlobalMainQueue: dispatch_queue_t {
  return dispatch_get_main_queue()
}

Next, create the dispatch group:

let queryGroup = dispatch_group_create()

Right before your queries execute, call:

dispatch_group_enter(queryGroup)

After your query executes, call:

dispatch_group_leave(queryGroup)

Then, handle your completion code:

dispatch_group_notify(queryGroup, GlobalMainQueue) {
  // completion code here
}
Renfro answered 13/2, 2016 at 22:7 Comment(0)
S
1

You should try to run the queries in parallel for better performance. In the completion handler for each one, call a common function that notes a query has completed. In that common function, when you determine that all of the queries have finished then you can proceed to the next step.

One simple approach to tracking the completion of the queries in the common function is to use a counter, either counting up from zero to the number of queries, or down from the number of total queries to zero.

Since HealthKit query handlers are called on an anonymous background dispatch queue, make sure you synchronize access to your counter, either by protecting it with a lock or by modifying the counter on a serial dispatch queue that you control, such as the main queue.

Sherrer answered 26/4, 2015 at 5:12 Comment(2)
Just a side note on the suggested approach of tracking the completion of the queries using a counter: You should pay attention to the fact that healthKit queries are asynchronous which mean you should protect the counter when changing it.Chanellechaney
@Chanellechaney Good point, I'm going to update the answer to include that.Sherrer

© 2022 - 2024 — McMap. All rights reserved.