I got a method which works like a refresher which uses the GCD pattern as shown below:
func getStepsForTheWeek() {
let concurrentQueue : dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(concurrentQueue, {
// Create an array of Days.
var days = [Day]()
dispatch_sync(concurrentQueue, {
print("first")
for day in 0...7 {
let date = self.getDate(day)
// Get the date the day after that day.
let endDate = self.getDateDayAfter(date)
// Create a Day.
var day = Day(date: date)
self.pedometer.queryPedometerDataFromDate(date, toDate: endDate, withHandler: {numberOfSteps, error in
print("fetching")
if error != nil {
print("There was an error requesting data from the pedometer: \(error)")
} else {
day.steps = numberOfSteps!.numberOfSteps as Int
days.append(day)
}
})
}
})
dispatch_sync(dispatch_get_main_queue(), {
print("second")
self.historyViewController.days = days
self.historyViewController.reloadHistory()
})
})
}
When the app starts the method works as it is intended to.
But when the app is in the background and when I'm going back to the app I got this Observer which calls the method again to refresh its content.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "appBecomeActive", name: UIApplicationWillEnterForegroundNotification, object: nil )
But everytime I do this the second code-block is running before the first one.
Any help?
i think you should do the UI update in the completion handler of the pedometer query
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
self.pedometer.queryPedometerDataFromDate(date, toDate: endDate, withHandler: { numberOfSteps, error in
if error != nil {
print("There was an error requesting data from the pedometer: \(error)")
} else {
let numberOfStepsThisDay = numberOfSteps?.numberOfSteps as! Int
day.steps = numberOfStepsThisDay
days.append(day)
}
dispatch_async(dispatch_get_main_queue(), {
self.historyViewController.days = self.days
self.historyViewController.reloadHistory()
})
})
})
you should always update the UI in the completion handler of the method if it has one, since you dont know whether it could be asynchronous or not (usually would be asynchronous if using a completion handler), you can probably drop the outer dispatch_async if there is no other code in it besides the pedometer query
I solve it.
In the query I check if the array of days is filled (e.g size of 8).
When filled the reload should be done. I also deleted all unnecessary sync-tasks. Much cleaner now. It was simpler than I thought.
func getStepsForTheWeek() {
// Create an array of Days.
var days = [Day]()
print("first")
// Fetch the total steps per day for 8 days (0 - 7).
for day in 0...7 {
// Get days date from today.
let date = self.getDate(day)
// Get the date the day after that day.
let endDate = self.getDateDayAfter(date)
// Create a Day.
var day = Day(date: date)
// Query the Pedometer for the total steps of that day.
self.pedometer.queryPedometerDataFromDate(date, toDate: endDate) {
(data, error) -> Void in
if(error == nil){
print("fetching")
day.steps = data!.numberOfSteps as Int
days.append(day)
if(days.count == 8){
dispatch_async(dispatch_get_main_queue(), { () -> Void in
print("finished")
self.historyViewController.days = days
self.historyViewController.reloadHistory()
})
}
}
}
}
}
Thanks to Fonix and gnasher729.
Related
Our app requires that the user carries out a daily task. If he/she completes the task, then at midnight a new task is released. If they don't, then come midnight they will continue seeing the previous task until they do.
I'm saving the current date in User Defaults and I'm using isDateInToday to heck if the date has changed:
func checkIfDayChanged() -> Bool {
if let date = UserDefaults.standard.object(forKey: "currentDate") as? Date {
var calendar = Calendar.current
calendar.timeZone = TimeZone.current
let isSameDay = calendar.isDateInToday(date)
if (isSameDay) {
return false
}
}
return true
}
In the majority of cases this works fine. However, there is a small group of users that are stuck with the same task even though they completed it the previous day. For some reason the app is not recognizing that the day is a new one.
Could this be a setting on the phone? Or do I need to change this code? Why wouldn't this be working in only a few devices?
UPDATE:
This is what happens when the daily task (a lesson) is completed:
func onComplete() {
let latestLesson = UserDefaults.standard.integer(forKey: "currentLesson")
if String(latestLesson) == lesson?.lessonNumber {
if (UserDefaults.standard.bool(forKey: "dayLessonCompleted") == false) {
UserDefaults.standard.set(true, forKey: "dayLessonCompleted")
lesson?.completed = true
}
}
}
Every day, the following function runs:
private func checkForMoreLessons() {
// Check the date
let newDay = checkIfDayChanged()
// If the day changed, check if user has watched the previous lesson
if newDay {
if (UserDefaults.standard.bool(forKey: "dayLessonCompleted") == true) {
// Save the new day
UserDefaults.standard.set(Date(), forKey:"currentDate")
// Add 1 to the current date
let currentLesson = UserDefaults.standard.integer(forKey: "currentLesson")
UserDefaults.standard.set(currentLesson + 1, forKey: "currentLesson")
UserDefaults.standard.set(false, forKey: "dayLessonCompleted")
}
}
}
This is my working code to fetch one item from NASA API I used the completion handler as presented on Apple Programming book.
class PhotoInfoController {
func fetchPhotoInfo(completion: #escaping (PhotoInfo?) -> Void) {
let baseURL = URL(string: "https://api.nasa.gov/planetary/apod")!
let query: [String:String] = [
"api_key" : "DEMO_KEY"
]
let url = baseURL.withQueries(query)!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
let jsonDecoder = JSONDecoder()
if let data = data,
let photoInfo = try? jsonDecoder.decode(PhotoInfo.self, from: data) {
completion(photoInfo)
} else {
print("Not found or data is not sanitazed.")
completion(nil)
}
}
task.resume()
}
}
The problem I having a hard time to figuring it out is how you can return an array go items (PhotoInfo) via a completion handler. This is my code so far:
class PhotoInfoController {
func fetchPhotoInfo(completion: #escaping ([PhotoInfo]?) -> Void) {
let baseURL = URL(string: "https://api.nasa.gov/planetary/apod")!
let currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-d"
var photoInfoCollection: [PhotoInfo] = []
for i in 0 ... 1 {
let modifiedDate = Calendar.current.date(byAdding: .day,value: -i ,to: currentDate)!
let stringDate = formatter.string(from: modifiedDate)
let query: [String:String] = [
"api_key" : "DEMO_KEY",
"date" : stringDate
]
let url = baseURL.withQueries(query)!
let task = URLSession.shared.dataTask(with: url) { (data,
response,
error) in
let jsonDecoder = JSONDecoder()
if let data = data,
let photoInfo = try? jsonDecoder.decode(PhotoInfo.self, from: data) {
photoInfoCollection.append(photoInfo)
} else {
print("Data was not returned")
}
}
task.resume()
}
completion(photoInfoCollection)
}
}
Any ideas or guide will greatly appreciated Thanks!
Code Implemented after suggestions:
class PhotoInfoController {
private let baseURL = URL(string: "https://api.nasa.gov/planetary/apod")!
private let currentDate = Date()
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-d"
return formatter
}()
private let jsonDecoder = JSONDecoder()
func fethPhotoInfo(itemsToFetch: Int, completion: #escaping ([PhotoInfo]?) -> Void) {
var count = 0
var photoInfoCollection: [PhotoInfo] = []
for i in 0 ... itemsToFetch {
let modifiedDate = Calendar.current.date(byAdding: .day, value: -i, to: currentDate)!
let query: [String : String] = [
"api_key" : "DEMO_KEY",
"date" : dateFormatter.string(from: modifiedDate)
]
let url = baseURL.withQueries(query)!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
if let data = data,
let photoInfo = try? self.jsonDecoder.decode(PhotoInfo.self, from: data) {
photoInfoCollection.append(photoInfo)
count += 1
if count == itemsToFetch {
completion(photoInfoCollection)
}
} else {
print("Data for \(self.dateFormatter.string(from: modifiedDate)) not made.")
}
}
task.resume()
}
}
}
Your code won't work as written.
You use a for loop to start 2 URLSession dataTask objects. Those tasks are async; the code that invokes the network request returns right away, before the request has even been sent.
Then outside your for loop you invoke your completion handler, before your network requests have even had a chance to be sent out. You will need a mechanism to keep track of the number of pending requests and invoke the completion handler when both requests have finished.
Consider this function that simulates what you are doing:
func createAsyncArray(itemCount: Int, completion: #escaping ([Int]) -> Void) {
var count = 0; //Keep track of the number of items we have created
var array = [Int]() //Create an empty results array
//Loop itemCount times.
for _ in 1...itemCount {
let delay = Double.random(in: 0.5...1.0)
//Delay a random time before creating a random number (to simulate an async network response)
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
//Add a random number 1...10 to the array
array.append(Int.random(in: 1...10))
//Increment the number of results we have added to the array
count += 1
print("In loop, count = \(count)")
//If we have have added enough items to the array, invoke the completion handler.
if count == itemCount {
completion(array)
}
}
}
print("at this point in the code, count = \(count)")
}
You might call that code like this:
let itemCount = Int.random(in: 2...10)
print("\(itemCount) items")
createAsyncArray(itemCount: itemCount) { array in
for i in 0..<itemCount {
print("array[\(i)] = \(array[i])")
}
}
Sample output from that function might look like this:
9 items
at this point in the code, count = 0
In loop, count = 1
In loop, count = 2
In loop, count = 3
In loop, count = 4
In loop, count = 5
In loop, count = 6
In loop, count = 7
In loop, count = 8
In loop, count = 9
array[0] = 8
array[1] = 6
array[2] = 5
array[3] = 4
array[4] = 7
array[5] = 10
array[6] = 2
array[7] = 4
array[8] = 7
Note that the output displays "at this point in the code, count = 0" before any of the entries have been added to the array. That's because each call to DispatchQueue.main.asyncAfter() returns immediately, before the code inside the closure has been executed.
The function above uses a local variable count to keep track of how many items have been added to the array. Once the count reaches the desired number, the function invokes the completion handler.
You should use an approach like that in your code.
Edit:
You should be aware that your network requests may complete out of order. Your code submits itemsToFetch+1 different requests. You have no idea what order those requests will finish in, and it is very unlikely that the requests will complete in the order they are submitted. If your second request completes faster than the first, its closure will execute first.
You're complicating it for yourself with trying to do everything in one method. Imagine you have the fetchPhotoInfo function working (it actually works, so, good job so far):
struct PhotoInfo: Codable {
let copyright: String
}
class PhotoInfoController {
private let base = "https://api.nasa.gov/planetary/apod"
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-d"
return formatter
}()
func fetchPhotoInfo(forDate date: Date, completion: #escaping (PhotoInfo?) -> Void) {
guard var components = URLComponents(string: base) else {
completion(nil)
return
}
components.queryItems = [
URLQueryItem(name: "api_key", value: "DEMO_KEY"),
URLQueryItem(name: "date", value: dateFormatter.string(from: date))
]
guard let url = components.url else {
completion(nil)
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
completion(nil)
return
}
let photoInfo = try? JSONDecoder().decode(PhotoInfo.self, from:data)
completion(photoInfo)
}
task.resume()
}
}
Your next goal is to fetch multiple photo info. Keep your class, keep your method and add another one which leverages what you already have. One way to achieve this is to use DispatchGroup:
func fetchPhotoInfo(forDates dates: [Date], completion: #escaping ([PhotoInfo]) -> Void) {
// Group of tasks
let taskGroup = DispatchGroup()
// Result of photos
var result: [PhotoInfo] = []
// For each date ...
dates.forEach {
// ... enter the group
taskGroup.enter()
// Fetch photo info
fetchPhotoInfo(forDate: $0) { photoInfo in
defer {
// Whenever the fetchPhotoInfo completion closure ends, leave
// the task group, but no sooner.
taskGroup.leave()
}
// If we've got the photo ...
if let photoInfo = photoInfo {
// ... add it to the result. We can safely add it here, because
// the fetchPhotoInfo completion block is called on the
// URLSession.shared.delegateQueue which has maxConcurrentOperationCount
// set to 1 by default. But you should be aware of this,
// do not rely on it and introduce some kind of synchronization.
result.append(photoInfo)
}
}
}
// At this point, we told the URLSession (via fetchPhotoInfo) that we'd like to
// execute data tasks. These tasks already started (or not, we don't know), but
// they didn't finish yet (most likely not, but we don't know either). That's
// the reason for our DispatchGroup. We have to wait for completion of all
// our asynchronous tasks.
taskGroup.notify(queue: .main) {
completion(result)
}
}
You can use it in this way:
let pic = PhotoInfoController()
pic.fetchPhotoInfo(forDate: Date()) { info in
print(String(describing: info))
}
pic.fetchPhotoInfo(forDates: [Date(), Date().addingTimeInterval(-24*60*60)]) { infos in
print(infos)
}
And the output is:
Optional(NASA.PhotoInfo(copyright: "Stephane Guisard"))
[NASA.PhotoInfo(copyright: "Stephane Guisard"), NASA.PhotoInfo(copyright: "Zixuan LinBeijing Normal U.")]
There's no error handling, you have to add it yourself.
Even if I did provide an answer to your question, I'm going to mark it as a duplicate of Wait until swift for loop with asynchronous network requests finishes executing. Your code to fetch single photo info works and you are just struggling to understand how to wait for multiple asynchronous tasks.
There seems to be a bug in the CMPedometer queryPedometerData() method. The method is returning 0 steps for certain end times, but the same query with the timestamp 1 second higher or lower returns the correct number of steps
e.g.
self.getStepsBetweenDates(NSDate(timeIntervalSince1970: 1543392126) as Date, date2: NSDate(timeIntervalSince1970: 1543393044) as Date) returns (Int) 1488
self.getStepsBetweenDates(NSDate(timeIntervalSince1970: 1543392126) as Date, date2: NSDate(timeIntervalSince1970: 1543393045) as Date) returns (Int) 0
self.getStepsBetweenDates(NSDate(timeIntervalSince1970: 1543392126) as Date, date2: NSDate(timeIntervalSince1970: 1543393046) as Date) returns (Int) 1488
getStepsBetweenDates method looks like this
func getStepsBetweenDates(_ date1: Date, date2: Date) -> Int{
let group = DispatchGroup()
group.enter()
var steps = 0
self.pedometer.queryPedometerData(from: date1, to: date2, withHandler: {
pData, error in
if let e = error{
print("Error querying pedometer", e.localizedDescription)
}else{
if let data = pData{
steps = Int(data.numberOfSteps)
}
group.leave()
}
})
_ = group.wait(timeout: DispatchTime.distantFuture)
return steps
}
queryPedometerData is an asynchronous call
Ordinarily you would not want to return the steps in the getStepsBetweenDates call because it is asynchronous.
If you changed the var steps = 0 to var steps = [some random int] then it's likely you'll get that number back instead of 0 because of the race condition set up.
More ideally would be to implement your code as a closure/callback or another form of asynchronous handling.
eg:
self.pedometer.queryPedometerData(from: date1, to: date2) { (data, error) in
// Do something here with data.numberOfSteps
}
Here's an article on Medium about async code:
https://medium.com/ios-os-x-development/managing-async-code-in-swift-d7be44cae89f
Well, the title really says it all, and I haven't been able to find an answer anywhere that works for me so I am turning to StackOverFlow. I am trying to get a users step count and assign that value to a UILabel. So here is some of my code (please note that this function is contained in another class as a result the label is not within the scope of this function):
func readTodayHealthData() -> Int {
var stepCount: Int = 0
func getStepsHealthData() {
let stepsUnit = HKUnit.countUnit()
let sumOption = HKStatisticsOptions.CumulativeSum
let stepsHealthDataQuery = HKStatisticsQuery(quantityType: stepsHealth, quantitySamplePredicate: predicate, options: sumOption) {
query, results, error in
if let sumQuantity = results?.sumQuantity() {
dispatch_async(dispatch_get_main_queue(), {
stepCount = sumQuantity.doubleValueForUnit(stepsUnit) * 2
})
}
}
healthKitStore?.executeQuery(stepsHealthDataQuery)
}
return stepCount
}
//Set UILabel Value
//**This code is in my View Controller which is in a separate class as a result this label is NOT within the scope of this function.**
myLabel.text = String(readTodayHealthData)
Then when I run the app on an actual device I see the label text is zero, and I know for a fact that I have done some walking today :). So, I think the issue is that when I try to set the labels value the function hasn't fully finished executing.
I know this because when I use the delay function and wait for two seconds I end up getting a value, but if I don't wait then I get a value of zero.
So the main question is: How do I check when a function is completely finished executing?
The thing is that the operation you're using is async, then you need to handle properly, you have two options here:
Update the UILabel in the completionHandler inside your function getStepsHealthData in the main thread because you are going to update the UI, like in this way:
func getStepsHealthData() {
var stepCount: Int = 0
let stepsUnit = HKUnit.countUnit()
let sumOption = HKStatisticsOptions.CumulativeSum
let stepsHealthDataQuery = HKStatisticsQuery(quantityType: stepsHealth, quantitySamplePredicate: predicate, options: sumOption) {
query, results, error in
if let sumQuantity = results?.sumQuantity() {
dispatch_async(dispatch_get_main_queue(), {
stepCount = sumQuantity.doubleValueForUnit(stepsUnit) * 2
//Set UILabel Value
myLabel.text = String(stepCount)
})
}
}
healthKitStore?.executeQuery(stepsHealthDataQuery)
}
And you don't need to return anything.
If you want to return the step counts from the function so you need to play a little with closures and modify your function like in the following way:
func getStepsHealthData(completion: (steps: Int) -> ()) {
var stepCount: Int = 0
let stepsUnit = HKUnit.countUnit()
let sumOption = HKStatisticsOptions.CumulativeSum
let stepsHealthDataQuery = HKStatisticsQuery(quantityType: stepsHealth, quantitySamplePredicate: predicate, options: sumOption) {
query, results, error in
if let sumQuantity = results?.sumQuantity() {
stepCount = sumQuantity.doubleValueForUnit(stepsUnit) * 2
completion(stepCount)
}
}
healthKitStore?.executeQuery(stepsHealthDataQuery)
}
And then you can call it like in this way from outside:
self.getStepsHealthData() { (steps) -> Void in
dispatch_async(dispatch_get_main_queue(), {
//Set UILabel Value
myLabel.text = String(stepCount)
})
}
I hope this help you.
The completion handlers (which you're already using) are called when the data is available. readTodayHealthData() will return well before that happens.
You need to use the data within the scope of the completion handler. For example, you could rewrite your function like this:
func updateLabel() {
var stepCount: Int = 0
func getStepsHealthData() {
let stepsUnit = HKUnit.countUnit()
let sumOption = HKStatisticsOptions.CumulativeSum
let stepsHealthDataQuery = HKStatisticsQuery(quantityType: stepsHealth, quantitySamplePredicate: predicate, options: sumOption) {
query, results, error in
if let sumQuantity = results?.sumQuantity() {
dispatch_async(dispatch_get_main_queue(), {
stepCount = sumQuantity.doubleValueForUnit(stepsUnit) * 2
self.myLabel.text = "\(stepCount)"
})
}
}
healthKitStore?.executeQuery(stepsHealthDataQuery)
}
}
This will update the label when the data has been returned.
You aren't actually trying to check if a function has finished executing though. That block that you pass to HKStatisticsQuery isn't really part of your original function. If you really want to prevent the myLabel.text= line from executing until after the block is called, you could use a semaphore, but that's a terrible solution to your actual problem. Why not have your block that's passed to HK update the label directly?
dispatch_async(dispatch_get_main_queue(), {
stepCount = sumQuantity.doubleValueForUnit(stepsUnit) * 2
myLabel.text = "\(stepCount)"
})
UPDATED at bottom on 01/30/16 # 7:40PM EST
So I'm trying to run a StatisticsQuery to get the total DistanceRunningWalking of the day stored in HealthKit and then store the results of that query in a variable, to be worked with later. The query seems to be working fine, as I've tested printing the results of the query (totalDistance) to a Label from within the function. The problem I'm running into is when trying to save the result to a variable instead.
Here is the code in my HealthKitManager.swift file:
import HealthKit
class HealthKitManager {
class var sharedInstance: HealthKitManager {
struct Singleton {
static let instance = HealthKitManager()
}
return Singleton.instance
}
let healthStore: HKHealthStore? = {
if HKHealthStore.isHealthDataAvailable() {
return HKHealthStore()
} else {
return nil
}
}()
let distanceCount = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)
let distanceUnit = HKUnit(fromString: "mi")
}
Code at top of ViewController: (this is the variable I'd like to save to)
let healthKitManager = HealthKitManager.sharedInstance
//Set up variable to contain result of query
var distanceTotalLength:Double?
Code in viewDidLoad:
//Run the function
requestHealthKitAuthorization()
//Set value of variable to query result
distanceTotalLength = queryDistanceSum()
Code in body of ViewController:
func requestHealthKitAuthorization() {
let dataTypesToRead = NSSet(objects: healthKitManager.distanceCount!)
healthKitManager.healthStore?.requestAuthorizationToShareTypes(nil, readTypes: dataTypesToRead as NSSet as? Set<HKObjectType>, completion: { [unowned self] (success, error) in
if success {
self.queryDistanceSum()
} else {
print(error!.description)
}
})
}
func queryDistanceSum() {
let sumOption = HKStatisticsOptions.CumulativeSum
let startDate = NSDate().dateByRemovingTime()
let endDate = NSDate()
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: [])
let statisticsSumQuery = HKStatisticsQuery(quantityType: healthKitManager.distanceCount!, quantitySamplePredicate: predicate, options: sumOption) {
[unowned self] (query, result, error) in
if let sumQuantity = result?.sumQuantity() {
dispatch_async(dispatch_get_main_queue(), {
let totalDistance = sumQuantity.doubleValueForUnit(self.healthKitManager.distanceUnit)
self.distanceTotalLength = totalDistance
})
}
}
healthKitManager.healthStore?.executeQuery(statisticsSumQuery)
}
On the last line return (distanceTotalLength)! I'm getting an error when launching the application which reads fatal error: unexpectedly found nil while unwrapping an Optional value. I've realized that this is most likely a problem dealing with scope (although I may be doing something else wrong, so please point anything out) but I'm failing to see/find the solution to the issue on my own.
Any help with this would be greatly appreciated, so thanks in advance!
UPDATE: 01/30/16 # ~7:40PM EST
Okay, so I've been working on trying to fix this myself and I've made a discovery: The code is definitely working. By assigning an initial value of 0 to distanceTotalLength at the top of the ViewController, I was able to run the application without getting the fatal error. However, when I then tried passing the value of distanceTotalLength to another view via a prepareForSegue function, I realized that it is being assigned after all. When I go to that view, it isn't using the initial value of 0, but the result of the query instead.
The way I tested this out was by setting up the variable: var distanceTotalLength:Double = 0 at the very top of my viewController, before viewDidLoad Then inside viewDidLoad I assigned the value to a label using distanceLabel.text = String(distanceTotalLength) and as I said, the label ends up reading 0. But when I transition to another view, passing the value of distanceTotalLength and printing the value out there, it works. On this second screen, it prints the result of the query, not 0.
So I'm assuming that the problem is that the query runs and then assigns the value AFTER the view has already loaded in with all of it's predefined values. Unfortunately, this is where I get stuck again. Anyone out there know how to help me out now that I've gotten this far?
You are right. The completionHandler closure that gets called after your HKStatisticsQuery has finished executing happens at an undermined later time. Think of the query execution as sending a letter in the mail to someone, then waiting for their response; it's not going to be immediate, but in the meantime you can go off and do other things.
To handle this in your code, add a completion closure of your own to the queryDistanceSum method. Then after setting the self.distanceTotalLength = totalDistance, call that closure. When implementing the code for the closure, add anything that needs to be done after the distance has been set, like update your UI.
func queryDistanceSum(completion: () -> Void) { // <----- add the closure here
let sumOption = HKStatisticsOptions.CumulativeSum
let startDate = NSDate().dateByRemovingTime()
let endDate = NSDate()
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: [])
let statisticsSumQuery = HKStatisticsQuery(quantityType: healthKitManager.distanceCount!, quantitySamplePredicate: predicate, options: sumOption) {
[unowned self] (query, result, error) in
if let sumQuantity = result?.sumQuantity() {
dispatch_async(dispatch_get_main_queue(), {
let totalDistance = sumQuantity.doubleValueForUnit(self.healthKitManager.distanceUnit)
self.distanceTotalLength = totalDistance
completion() // <----- call the closure here
})
}
}
healthKitManager.healthStore?.executeQuery(statisticsSumQuery)
}
// Then whenever you need to update the distance sum call the function
// with the closure, then handle the result as needed
queryDistanceSum { () -> () in
// distanceTotalLength has now been set.
// Update UI for new distance value or whatever you need to do
}
Whenever you implement a closure you have to assume that the code in the closure will be executed at a later time.