Search code examples
iosswiftfirebasegrand-central-dispatch

How can I guarantee that a Swift closure (referencing Firebase) fully executes before I move on?


I have multiple query snapshots with closures and some of them are using the data supplied by the query that came before it.

I have read up on GCD and I've tried to implement a DispatchGroup with .enter() and .leave() but I am apparently doing something wrong.

If somebody can help me by laying out exactly how to force one task to be performed before another, that would solve my problem.

If you can't tell, I am somewhat new to this so any help is greatly appreciated.

//MARK: Get all userActivities with distance(All Code)
static func getAllChallengesWithDistanceAllCode(activity:String, completion: @escaping ([Challenge]) -> Void) {
    let db = Firestore.firestore()
    let currUserID = Auth.auth().currentUser!.uid
    var currUserName:String?
    var distanceSetting:Int?
    var senderAverage:Double?
    var senderBestScore:Int?
    var senderMatchesPlayed:Double?
    var senderMatchesWon:Double?
    var senderWinPercentage:Double?
    var validUserActivities = [Challenge]()
    
    db.collection("users").document(currUserID).getDocument { (snapshot, error) in
        if error != nil || snapshot == nil {
            return
        }
        currUserName = snapshot?.data()!["userName"] as? String
        distanceSetting = snapshot?.data()!["distanceSetting"] as? Int
    }
    db.collection("userActivities").document(String(currUserID + activity)).getDocument { (snapshot, error) in
        //check for error
        //MARK: changed snapshot to shapshot!.data() below (possible debug tip)
        if error != nil {
            //is error or no data..??
            return
        }
        if snapshot!.data() == nil {
            return
        }
        //get profile from data proprty of snapshot
        if let uActivity = snapshot!.data() {
            senderBestScore = uActivity["bestScore"] as? Int
            senderMatchesPlayed = uActivity["played"] as? Double
            senderMatchesWon = uActivity["wins"] as? Double
            senderAverage = uActivity["averageScore"] as? Double
            senderWinPercentage = round((senderMatchesWon! / senderMatchesPlayed!) * 1000) / 10
        }
    }
    
    if distanceSetting != nil {
        db.collection("userActivities").whereField("activity", isEqualTo: activity).getDocuments { (snapshot, error) in
            if error != nil {
                print("something went wrong... \(String(describing: error?.localizedDescription))")
                return
            }
            if snapshot == nil || snapshot?.documents.count == 0 {
                print("something went wrong... \(String(describing: error?.localizedDescription))")
                return
            }
            if snapshot != nil && error == nil {
                let uActivitiesData = snapshot!.documents
                for uActivity in uActivitiesData {
                    let userID = uActivity["userID"] as! String
                    UserService.determineDistance(otherUserID: userID) { (determinedDistance) in
                        if determinedDistance! <= distanceSetting! && userID != currUserID {
                            var x = Challenge()
                            //Sender
                            x.senderUserID = currUserID
                            x.senderUserName = currUserName
                            x.senderAverage = senderAverage
                            x.senderBestScore = senderBestScore
                            x.senderMatchesPlayed = senderMatchesPlayed
                            x.senderMatchesWon = senderMatchesWon
                            x.senderWinPercentage = senderWinPercentage
                            //Receiver
                            x.receiverUserID = userID
                            x.receiverUserName = uActivity["userName"] as? String
                            x.receiverAverage = uActivity["averageScore"] as? Double
                            x.receiverBestScore = uActivity["bestScore"] as? Int
                            if (uActivity["played"] as! Double) < 1 || (uActivity["played"] as? Double) == nil {
                                x.receiverMatchesPlayed = 0
                                x.receiverMatchesWon = 0
                                x.receiverWinPercentage = 0
                            } else {
                                x.receiverMatchesPlayed = uActivity["played"] as? Double
                                x.receiverMatchesWon = uActivity["wins"] as? Double
                                x.receiverWinPercentage = ((uActivity["wins"] as! Double) / (uActivity["played"] as! Double) * 1000).rounded() / 10
                            }
                            if uActivity["playStyle"] as? String == nil {
                                x.receiverPlayStyle = "~No PlayStyle~"
                            } else {
                                x.receiverPlayStyle = uActivity["playStyle"] as! String
                            }
                            x.activity = activity
                            
                            //append to array
                            validUserActivities.append(x)
                        }
                    }
                }
                completion(validUserActivities)
            }
        }
    }
}

Solution

  • I solved this by doing the following:

    1. Add a constant before the for-in loop counting the total number of query results
        let documentCount = snapshot?.documents.count
    
    1. Add a counter before the for-in loop starting at 0
        var runCounter = 0
    
    1. Increment the counter with each iteration at the beginning of the for-in loop
        runCounter += 1
    
    1. Add code to the end of the for-in loop to call the completion handler to return the results
        if runCounter == documentCount {
                                                    
            completion(validChallenges)
        }