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. 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 STUFFto the end of the loop inside thegetDocumentsclosure or add a completion handler.