2

I understand that the Firebase getDocument call is Async, so I'm trying to figure out how to essentially wait until the call finishes executing, and then move on to doing other stuff.

I have tried making use of DispatchGroup() and entering/leaving the group, but I can't seem to get it to work correctly. I have something like the following:

let myGroup = DispatchGroup()
let usersRef = self.db.collection("Users").document("Users").collection("Users")
if self.testCondition == false {
    self.errorMessage = "error"
} else{
    usersRef.getDocuments {(snap, err) in
        myGroup.enter()
        //basically getting every username
        for document in snap!.documents{
            let user = document["username"] as! String
            let userRef = usersRef.document(user)
            userRef.getDocument { (snapshot, err) in
                if err != nil {
                    print(err)
                } else {
                    let sample = snapshot!["sample"] as! String
                    if sample == 'bad' {
                        self.errorMessage = "error"
                    }
                }
            }
        }
        myGroup.leave()
    }
    print("what4")
    //I would like it so that I can execute everything in a code block like this
    //after the async call finishes
    myGroup.notify(queue: .main) {
        print("Finished all requests.")
        //THEN DO MORE STUFF
    }
}

How can I modify the placement myGroup.enter() and myGroup.leave() in this so that, after the Firebase call has finished, I can continue executing code?

Thanks!

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Evan
  • 1,892
  • 2
  • 19
  • 40
  • This is a good example for the misuse of `DispatchGroup`. It's **not** the purpose of this API to force a single asynchronous task to become synchronous. And to *wait* for the completion of an asynchronous task is generally the wrong approach. The recommended way is to move `//THEN DO MORE STUFF` to the end of the loop inside the `getDocuments` closure or add a completion handler. – vadian Jul 21 '20 at 06:57

1 Answers1

5

This explains the DispatchGroup() a little bit.

You just have one litte mistake in your code then it should be working. Make sure to enter() the group outside of the Firebase getDocuments() call. As this already makes the request and takes time thus the process will continue.

This little simple example should help you understand it:

func dispatchGroupExample() {
        
        // Initialize the DispatchGroup
        let group = DispatchGroup()
        
        print("starting")
        
        // Enter the group outside of the getDocuments call
        group.enter()
        
        let db = Firestore.firestore()
        let docRef = db.collection("test")
        docRef.getDocuments { (snapshots, error) in
            
            if let documents = snapshots?.documents {
                for doc in documents {
                    print(doc["name"])
                }
            }
            
            // leave the group when done
            group.leave()
        }
        
        // Continue in here when done above
        group.notify(queue: DispatchQueue.global(qos: .background)) {
            print("all names returned, we can continue")
        }
    }

When waiting for multiple asynchronous calls use completing in the asynchronous function which you let return as soon as you leave the group. Full eg. below:

class Test {
    
    init() {
        self.twoNestedAsync()
    }
    
    func twoNestedAsync() {
        let group = DispatchGroup() // Init DispatchGroup
        
        // First Enter
        group.enter()
        print("calling first asynch")
        
        self.dispatchGroupExample() { isSucceeded in
            
            // Only leave when dispatchGroup returns the escaping bool
            
            if isSucceeded {
                group.leave()
            } else {
                // returned false
                group.leave()
            }
        }
        
        // Enter second
        group.enter()
        print("calling second asynch")
        
        self.waitAndReturn(){ isSucceeded in
            
            // Only return once the escaping bool comes back
            if isSucceeded {
                group.leave()
            } else {
                //returned false
                group.leave()
            }
            
        }
        
        group.notify(queue: .main) {
            print("all asynch done")
        }
    }
    
    // Now added escaping bool which gets returned when done
    func dispatchGroupExample(completing: @escaping (Bool) -> Void) {
        
        // Initialize the DispatchGroup
        let group = DispatchGroup()
        
        print("starting")
        
        // Enter the group outside of the getDocuments call
        group.enter()
        
        let db = Firestore.firestore()
        let docRef = db.collection("test")
        docRef.getDocuments { (snapshots, error) in
            
            if let documents = snapshots?.documents {
                for doc in documents {
                    print(doc["name"])
                }

                // leave the group when succesful and done
                group.leave()
            }
            
            if let error = error {
                // make sure to handle this
                completing(false)
                group.leave()
            }
        }
        
        // Continue in here when done above
        group.notify(queue: DispatchQueue.global(qos: .background)) {
            print("all names returned, we can continue")
            
            //send escaping bool.
            completing(true)
        }
    }
    
    func waitAndReturn(completing: @escaping (Bool) -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: {
            print("Done waiting for 2 seconds")
            completing(true)
        })
    }
}

This gives us the following output:

DispatchGroup with completing and escaping output

Simon
  • 1,754
  • 14
  • 32
  • That's a great example. can you give a similiar one when we have nested asynch calls? Say two asynch calls nested. – Coder Jul 03 '20 at 09:14
  • 1
    I added an example. Wasn't sure if I understand you correctly. Let me know if this satisfies your needs – Simon Jul 03 '20 at 09:51
  • Stay Blessed brother! That helped me! – Coder Jul 03 '20 at 10:05
  • hey @Simon thanks so much for this, was super helpful and let me do some of what I was trying do. I realized that I think I'm also working with nested async functions, see here: https://stackoverflow.com/questions/62723265/working-with-nested-async-firebase-calls-swiftui so should I be trying to mirror something like your second example? – Evan Jul 03 '20 at 22:15
  • Yes I'd recommend you something like the multi async call I added here. Nonetheless I also answered your other question – Simon Jul 04 '20 at 09:28