1

I am trying to query data from firebase inside a for loop, my problem is since the queries take time to connect, swift is jumping over the queries and coming back later to do them. This creates the problem where my loop counter is ticking up but the queries are being saved for later, when the queries finally do get executed, the counter variable is all out of wack.

Where the code is being skipped is right after the query, where I am trying to append to an array.

func getSelectedData() {
    
    var exerciseIndex = 0
    
    for i in 0...Master.exercises.count - 1 {
        if Master.exercises[i].name == self.exerciseName {
            exerciseIndex = i
        }
    }
    
    let numOfSets = Master.exercises[exerciseIndex].totalSets
    
    // For each date record
    for count in 0...self.returnedExercises.count-1 {
        
        // Creates a new dataSet
        dataSet.append(dataSetStruct())
        
        dataSet[count].date = returnedExercises[count]
        
        for number in 0...(numOfSets - 1) {
            
            // Retrives the reps
            let repsDbCallHistory = db.collection("users").document("\(userId)").collection("ExerciseData").document("AllExercises").collection(exerciseName).document(returnedExercises[count]).collection("Set\(number + 1)").document("reps")
            
            repsDbCallHistory.getDocument { (document, error) in
                if let document = document, document.exists {
                    // For every document (Set) in the database, copy the values and add them to the array
                    
                    let data:[String:Any] = document.data()!
                    
                    self.dataSet[count].repsArray.append(data["Reps\(number + 1)"] as! Int)
                }
                else {
                    // error
                }
            }
            
            //Retrives the weights
            let weightsDbCallHistory = db.collection("users").document("\(userId)").collection("ExerciseData").document("AllExercises").collection(exerciseName).document(returnedExercises[count]).collection("Set\(number + 1)").document("weights")
            
            weightsDbCallHistory.getDocument { (document, error) in
                if let document = document, document.exists {
                    // For every document (Set) in the database, copy the values and add them to the array
                    
                    let data:[String:Any] = document.data()!
                    
                    self.dataSet[count].weightsArray.append(data["Weight\(number + 1)"] as! Float)
                    
                    self.updateGraph()
                }
                else {
                    // error
                }
            }
        }
    }
}

I even tried breaking out the query into another function but this doesn't seem to fix the issue.

Any help is appreciated, thanks.

EDIT:

    func getSelectedData() {
    
    if returnedExercises.count > 0 {
        
        // Create a dispatch group
        let group = DispatchGroup()
        
        print("Getting Data")
        
        // For each date record
        for count in 0...self.returnedExercises.count-1 {
            
            // Creates a new dataSet
            self.dataSet.append(dataSetStruct())
            
            self.dataSet[count].date = self.returnedExercises[count]
            
            for number in 0...(self.numOfSets - 1) {
                
                print("At record \(count), set \(number)")
                
                // Enter the group
                group.enter()
                
                // Start the dispatch
                DispatchQueue.global().async {
                    
                    // Retrives the reps
                    let repsDbCallHistory = self.db.collection("users").document("\(self.userId)").collection("ExerciseData").document("AllExercises").collection(self.exerciseName).document(self.returnedExercises[count]).collection("Set\(number + 1)").document("reps")
                    
                    repsDbCallHistory.getDocument { (document, error) in
                        if let document = document, document.exists {
                            // For every document (Set) in the database, copy the values and add them to the array
                            
                            let data:[String:Any] = document.data()!
                            
                            self.dataSet[count].repsArray.append(data["Reps\(number + 1)"] as! Int)
                            
                            print("Getting data: \(number)")
                            
                            group.leave()
                        }
                        else {
                            // error
                        }
                    }
                }
        group.wait()
        print("Finished getting data")
    }
}

I tried to simplify the function for now and only have one database call in the function to try the dispatch groups. I am not sure why firebase is doing this but the code never executes the group.leave, the program just sits idle. If I am doing something wrong please let me know, thanks.

This is what the print statements are showing:

Getting Data
At record 0, set 0
At record 0, set 1
At record 0, set 2
At record 1, set 0
At record 1, set 1
At record 1, set 2

print("Getting data: (number)") is never being executed for some reason.

I am thinking that maybe firebase calls are ran on a separate thread or something, which would made them pause execution as well, but that's just my theory

EDIT2::

    func getOneRepMax(completion: @escaping (_ message: String) -> Void) {
    
    if returnedOneRepMax.count > 0 {
        
        print("Getting Data")
        
        // For each date record
        for count in 0...self.returnedOneRepMax.count-1 {
            
            // Creates a new dataSet
            oneRPDataSet.append(oneRepMaxStruct())
            
            oneRPDataSet[count].date = returnedOneRepMax[count]
            
            // Retrives the reps
            let oneRepMax = db.collection("users").document("\(userId)").collection("UserInputData").document("OneRepMax").collection(exerciseName).document(returnedOneRepMax[count])
            
            oneRepMax.getDocument { (document, error) in
                if let document = document, document.exists {
                    // For every document (Set) in the database, copy the values and add them to the array
                    
                    let data:[String:Any] = document.data()!
                    
                    self.oneRPDataSet[count].weight = Float(data["Weight"] as! String)!
                    
                    print("Getting data: \(count)")
                    
                    completion("DONE")
                    
                    self.updateGraph()
                }
                else {
                    // error
                }
            }
        }
    }
}

I tried using completion handlers for a different function and it is also not working properly.

                self.getOneRepMax(completion: { message in
                   print(message)
            })
            
            print("Finished getting data")

The order that the print statements should go:
Getting Data
Getting data: 0
Done
Getting data: 1
Done
Finished getting data

The order that the print statements are coming out right now:
Getting Data
Finished getting data
Getting data: 1
Done
Getting data: 0
Done

I am not even sure how it is possible that the count is backwards since my for loop counts up, what mistake am I making?

Hayden
  • 11
  • 2
  • Please post your code in text form rather than screenshots (which are tough to read on phones and an accessibility problem) – jnpdx Mar 12 '21 at 20:13
  • Does this answer your question? [Returning data from async call in Swift function](https://stackoverflow.com/questions/25203556/returning-data-from-async-call-in-swift-function) – jnpdx Mar 12 '21 at 20:14
  • I changed the image as suggested, however the link did not seem to help, I tried implementing completion handlers but the same issue is still happening @jnpdx – Hayden Mar 13 '21 at 20:23
  • I suggest you show your updated code with the completion handlers – jnpdx Mar 13 '21 at 20:24
  • Hi @jnpdx, I added the code where I tried to use the completion handlers – Hayden Mar 15 '21 at 19:01
  • You are not going to be able to chain the async calls in a specific order without more work chaining them together. It is not a trivial concept. Is there any way that you can re-architect things without the need for them to go in order? – jnpdx Mar 15 '21 at 19:21
  • Sorry for the confusion, I do not need the async calls to be in order, I was just pointing out that it was strange that it was backwards. However, I do need "Finished getting data" to print only after the async function is actually complete, this is what I am trying to achieve by using the completion handlers, but it seems it still isn't working. @jnpdx – Hayden Mar 15 '21 at 21:21
  • That's because they return asynchronously. You can't force them to be sync even by wrapping them in `DispatchGroup` (which you render irrelevant by then dispatching async on the global queue again anyway) – jnpdx Mar 15 '21 at 21:24

1 Answers1

0

I think what you need are Dispatch Groups.

let dispatchGroup1 = DispatchGroup()
let dispatchGroup2 = DispatchGroup()

dispatchGroup1.enter() 
firebaseRequest1() { (_, _) in  
   doThings()
dispatchGroup1.leave() 
}

dispatchGroup2.enter() 
dispatchGroup1.notify(queue: .main) {
firebaseRequest2() { (_, _ ) in
doThings()
dispatchGroup2.leave() 
}
dispatchGroup2.notify(queue: .main) {
completionHandler()
}
George
  • 328
  • 2
  • 8
  • Hi George, I edited my post with Dispatch groups but there are still some errors if you could have a look, thanks. @George – Hayden Mar 15 '21 at 16:35