Synchronization enforces a temporal ordering among events. If you were using threads directly, you would use mutexes, semaphores, and condition variables (see Chapter 20: Multithreading) to express the desired synchronization.
Grand Central Dispatch allows for a simpler approach. To serialize access to a resource, use a serial queue associated with the resource rather than a mutex.
To wait for an asynchronous event, use a dispatch source rather than a condition variable.
To wait for a task to complete, simply dispatch the follow-up task from the end of that task. If a task depends on several tasks that can execute concurrently, target them all to a concurrent queue and use a dispatch group to asynchronously ...