When you use just one async function, you can simply use a completionHandler,
but you can also call several async functions and make the completionHandler run after the very last one finishes.
What kind of situation is that?

When you call just one async function, putting the function to run after it finishes into the completionHandler works fine,
but when you call several async functions (for example, in a for loop), it will look like this.

There's no guarantee that an async function called earlier finishes earlier, and if you send the same function in a for loop, it's weird to put the completionHandler on just one of them, and there's no guarantee it would work the way you want.
And if you put a completionHandler on every function, it will be called as many times as the number of functions you called.
In this case, you can use DispatchGroup.
When you call async functions through DispatchQueue, you can put them into the group directly, but for functions that are already set to run asynchronously so you can't specify a group (firebase, urlsession, etc.), you can use the enter() and leave() functions. I'll only explain the latter.

The usage follows the structure in the picture.
When calling each async function, call enter() to tell the DispatchGroup that not all the functions have finished yet.
Then each async function should have a completionHandler, so call leave() in that completionHandler to tell the group that its own function is done.
Then the DispatchGroup knows that all the functions have finished, and it calls notify, which can do the same job as a completionHandler.
Looking at the code,
let dispatchGroup = DispatchGroup() for i in 0..<4 { dispatchGroup.enter() fetchPost(forPost: document.documentID) { post in dispatchGroup.leave() } } dispatchGroup.notify(queue: .main) { print("모든 함수 종료되었을 때 호출할 함수") }