GCD (Grand Central Dispatch)
GCD (Grand Central Dispatch) is a Low level API basically used to manage the concurrency of the application. It manages the heavy task at the background.
We use DispatchQueue for the operation for the GCD . DispatchQueue is the method object which is used to manage the task execution in serial or concurrent manner basically in FIFO order.
Thread :
Thread is basically the running the task at the different core. it is basically running or execution of code .
In Swift\’s Grand Central Dispatch (GCD), tasks can run on different threads. Two important ones are:
- Main Thread (
DispatchQueue.main): this is basically called as UI thread where we show the data to the user from the application- Runs on the UI thread.
- Used for updating UI elements (buttons, labels, views, etc.).
- Blocking the main thread will freeze the UI.
DispatchQueue.main.async {
print("Updating UI on the main thread")
myLabel.text = "Updated!"
}
- Global Queue (Background Threads) (
DispatchQueue.global()) : This is the thread basically used for the logic calculation or service call at the background.- Runs tasks on background threads.
- Used for heavy operations like network requests, database queries, and image processing.
- Does not block the UI thread.
| QoS Level | Priority | Use Case |
|---|---|---|
.userInteractive | Highest | UI animations, quick tasks |
.userInitiated | High | Data loading, quick computations |
.default | Medium | Normal tasks (default) |
.utility | Low | Large downloads, parsing |
.background | Lowest | Data sync, backups |
Quality of Service
- User Interactive
- User Initiated
- Utility
- Background
User Interactive : it is used when there is user interaction related task or Animation related work takes place
DispatchQueue.global(qos: .userInteractive).async{
print("User Interactive task")
}User Initiated : It is to be used when we required immediate result. basically during the scrolling or searching.
DispatchQueue.global(qos: .userInitiated).async {
print("User Initiated task")
}Utility : This is used for long running task basically in downloading where user is aware of the proccess which is not at the high priority.
DispatchQueue.global(qos: .utility).async {
print("User Utility task")
}Background : This is used to run the task at the background. This task are not visible to the user
DispatchQueue.global(qos: .background).async {
print("Background thread")
}
There are 2 more Quality of Service which are rarely used they are
- Default
- Unspecified
Example: Running a Task on a Background Thread
DispatchQueue.global(qos: .background).async {
print("Performing a background task")
DispatchQueue.main.async {
print("Returning to main thread for UI update")
}
}
✅ Use Case:
- Run heavy tasks on a global queue.
- Switch back to
mainqueue for UI updates.
3. Main Thread vs. Global Queue Example
print("Task 1 - Running on \\(Thread.isMainThread ? "Main" : "Background")")
DispatchQueue.global(qos: .userInitiated).async {
print("Task 2 - Running on \\(Thread.isMainThread ? "Main" : "Background")")
DispatchQueue.main.async {
print("Task 3 - Running on \\(Thread.isMainThread ? "Main" : "Background")")
}
}
✅ Possible Output:
Task 1 - Running on Main
Task 2 - Running on Background
Task 3 - Running on Main
🔹 Task 1 runs on the main thread.
🔹 Task 2 runs on a background thread.
🔹 Task 3 returns to the main thread.
4. When to Use Each?
| Feature | Main Thread (DispatchQueue.main) | Global Queue (DispatchQueue.global()) |
|---|---|---|
| UI Updates | ✅ Yes | ❌ No |
| Network Calls | ❌ No | ✅ Yes |
| Image Processing | ❌ No | ✅ Yes |
| File Reading | ❌ No | ✅ Yes |
5. Avoid Blocking the Main Thread
❌ Bad Example (Blocking Main Thread)
print("Start")
DispatchQueue.main.sync {
print("This will cause a deadlock!")
}
print("End")
🚨 This will freeze the app! The main queue is waiting for itself, causing a deadlock.
6. Correct Usage: Running Background Tasks and Updating UI
DispatchQueue.global(qos: .userInitiated).async {
let result = heavyComputation()
DispatchQueue.main.async {
updateUI(with: result)
}
}
✅ Best Practice:
- Run heavy tasks in the global queue.
- Switch back to
DispatchQueue.mainfor UI updates.
🚀 Final Summary
| Feature | DispatchQueue.main (Main Thread) | DispatchQueue.global() (Global Queue) |
|---|---|---|
| Runs on UI Thread? | ✅ Yes | ❌ No |
| Blocks UI? | ❌ No (unless sync is used) | ❌ No |
| Handles Heavy Work? | ❌ No | ✅ Yes |
| Use Case | UI Updates | Background Processing |
Swift Interview Questions & Answers on Main Thread & Global Thread
1. What is the difference between the main thread and a global thread in Swift?
✅ Answer:
- Main Thread (
DispatchQueue.main)- Runs on the UI thread.
- Used for UI updates, user interactions.
- Blocking the main thread will freeze the UI.
- Global Queue (
DispatchQueue.global())- Runs tasks in the background (multithreaded).
- Used for heavy operations like network requests, database queries, and image processing.
- Improves performance by preventing UI lag.
Example:
DispatchQueue.global().async {
print("Background Task")
DispatchQueue.main.async {
print("Back to Main Thread for UI Update")
}
}
2. Can we update the UI from a background thread?
✅ Answer:
No, UI updates must be done on the main thread.
Updating UI from a background thread will cause unexpected behavior or crashes.
Example (Wrong):
DispatchQueue.global().async {
myLabel.text = "Updating from background" // ❌ UI update in background thread (BAD)
}
Example (Correct):
DispatchQueue.global().async {
let result = heavyComputation()
DispatchQueue.main.async {
myLabel.text = result // ✅ UI update on the main thread (GOOD)
}
}
3. What is the Quality of Service (QoS) in Global Queues?
✅ Answer:
Quality of Service (QoS) defines task priority in DispatchQueue.global().
| QoS | Priority | Use Case |
|---|---|---|
.userInteractive | Highest | UI animations, instant feedback |
.userInitiated | High | Data loading, quick computations |
.default | Medium | Normal tasks (default) |
.utility | Low | Large downloads, background tasks |
.background | Lowest | Syncing, maintenance tasks |
Example:
DispatchQueue.global(qos: .background).async {
print("Background Task Running...")
}
4. What happens if we call sync on DispatchQueue.main?
✅ Answer:
It causes a deadlock because the main thread waits for itself to finish.
Example (Deadlock 🚨):
print("Start")
DispatchQueue.main.sync {
print("This will never execute!") // ❌ Deadlock occurs here
}
print("End") // ❌ This will never run
✅ Fix: Use async instead of sync to prevent deadlocks.
5. What is the difference between DispatchQueue.main.sync and DispatchQueue.main.async?
✅ Answer:
| Function | Blocks Execution? | Runs Task On |
|---|---|---|
DispatchQueue.main.sync | ✅ Yes (Waits) | 🛑 Dangerous – Can cause Deadlock |
DispatchQueue.main.async | ❌ No (Non-blocking) | ✅ Main Thread (Safe for UI Updates) |
✅ Example: Safe UI Updates with async
DispatchQueue.main.async {
print("UI Update on Main Thread")
}
6. How do you perform a long-running task in the background and then update the UI?
✅ Answer:
Use DispatchQueue.global().async for background work and DispatchQueue.main.async for UI updates.
Example:
DispatchQueue.global(qos: .userInitiated).async {
let data = fetchDataFromServer()
DispatchQueue.main.async {
updateUI(with: data)
}
}
7. What is the difference between a Serial and a Concurrent Queue in GCD?
✅ Answer:
| Queue Type | Description |
|---|---|
| Serial Queue | Executes tasks one at a time, in order. |
| Concurrent Queue | Executes multiple tasks simultaneously. |
Example: Serial Queue
let serialQueue = DispatchQueue(label: "com.example.serialQueue")
serialQueue.async {
print("Task 1 started")
sleep(2)
print("Task 1 completed")
}
serialQueue.async {
print("Task 2 started")
}
✅ Output (Always in Order)
Task 1 started
Task 1 completed
Task 2 started
Example: Concurrent Queue
let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
concurrentQueue.async {
print("Task 1 started")
sleep(2)
print("Task 1 completed")
}
concurrentQueue.async {
print("Task 2 started")
}
✅ Possible Output (Order may vary)
Task 1 started
Task 2 started
Task 1 completed
8. Can you call DispatchQueue.main.sync inside a background thread?
✅ Answer:
Yes, calling sync on DispatchQueue.main from a background thread is safe.
Example (Safe):
DispatchQueue.global().async {
print("Background Task Started")
DispatchQueue.main.sync {
print("Back to Main Thread for UI Update")
}
}
✅ Output:
Background Task Started
Back to Main Thread for UI Update
🚨 Warning: Never call sync inside DispatchQueue.main itself (deadlock risk).
9. What happens if you create a custom DispatchQueue?
✅ Answer:
A custom queue can be serial (default) or concurrent.
Example: Serial Custom Queue
let mySerialQueue = DispatchQueue(label: "com.example.mySerialQueue")
mySerialQueue.async {
print("Task 1 started")
sleep(1)
print("Task 1 completed")
}
mySerialQueue.async {
print("Task 2 started")
}
✅ Output (Always in order)
Task 1 started
Task 1 completed
Task 2 started
10. What is the main advantage of using DispatchQueue.global(qos:)?
✅ Answer:
It allows you to execute tasks asynchronously on a background thread, improving app performance by preventing UI blocking.
🚀 Summary
| Concept | Main Thread (DispatchQueue.main) | Global Queue (DispatchQueue.global()) |
|---|---|---|
| Runs on UI Thread? | ✅ Yes | ❌ No |
| Blocks UI? | ❌ No | ❌ No |
| Handles Heavy Work? | ❌ No | ✅ Yes |
| Can Execute Concurrently? | ❌ No | ✅ Yes |
| Use Case | UI Updates | Background Tasks |
🔥 Key Takeaways for Interviews:
- UI updates must always be on the main thread (
DispatchQueue.main). - Heavy tasks should run in the background (
DispatchQueue.global()). - Calling
syncon the main thread causes a deadlock! - Use
asyncto avoid blocking the main thread. - Use QoS to control task priority in
DispatchQueue.global(qos:).
Advanced Swift Interview Questions & Answers on Main & Global Threads (GCD)
11. What happens if you call DispatchQueue.global().sync inside another DispatchQueue.global().async?
✅ Answer:
- It works fine because
DispatchQueue.global()is a concurrent queue and does not block itself.
Example (Safe Execution)
DispatchQueue.global().async {
print("Task 1 - Started")
DispatchQueue.global().sync {
print("Task 2 - Synchronous Execution")
}
print("Task 1 - Completed")
}
✅ Expected Output:
Task 1 - Started
Task 2 - Synchronous Execution
Task 1 - Completed
🔹 Why does it work?
DispatchQueue.global()is a concurrent queue, so callingsyncinsideasyncdoes not cause a deadlock.
12. What is the difference between DispatchQueue.global().asyncAfter and DispatchQueue.main.asyncAfter?
✅ Answer:
asyncAfterdelays execution without blocking the current thread.DispatchQueue.global().asyncAfterexecutes a background task after a delay.DispatchQueue.main.asyncAfterexecutes a UI-related task after a delay.
Example: Background Execution (Global Queue)
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
print("Background task executed after 3 seconds")
}
Example: UI Execution (Main Queue)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print("UI update after 2 seconds")
}
✅ Use Cases:
- Use
DispatchQueue.global().asyncAfterfor delayed background tasks. - Use
DispatchQueue.main.asyncAfterfor delayed UI updates.
13. What are the different ways to create a queue in GCD?
✅ Answer:
You can create a custom dispatch queue in different ways:
1️⃣ Serial Queue (Default)
let serialQueue = DispatchQueue(label: "com.example.serialQueue")
serialQueue.async {
print("Task 1 - Executing serially")
}
serialQueue.async {
print("Task 2 - Executing serially")
}
✅ Tasks execute one after another.
2️⃣ Concurrent Queue
let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
concurrentQueue.async {
print("Task 1 - Executing concurrently")
}
concurrentQueue.async {
print("Task 2 - Executing concurrently")
}
✅ Tasks execute at the same time.
3️⃣ Main Queue (For UI)
DispatchQueue.main.async {
print("Main thread execution")
}
4️⃣ Global Queue (Background Processing)
DispatchQueue.global(qos: .background).async {
print("Background task executing...")
}
14. How do you prevent race conditions in a concurrent queue?
✅ Answer:
Use DispatchQueue.sync on a serial queue to synchronize access.
🔹 Problem: Race Condition
var sharedResource = 0
DispatchQueue.global().async {
sharedResource += 1
}
DispatchQueue.global().async {
sharedResource += 1
}
print(sharedResource) // ❌ Unpredictable output
🔹 Solution: Use a Serial Queue
let syncQueue = DispatchQueue(label: "com.example.syncQueue")
syncQueue.sync {
sharedResource += 1
}
syncQueue.sync {
sharedResource += 1
}
print(sharedResource) // ✅ Predictable output: 2
✅ Why?
- The serial queue ensures tasks execute one by one, avoiding conflicts.
15. How do you use DispatchGroup to wait for multiple tasks to complete?
✅ Answer:
A DispatchGroup allows multiple asynchronous tasks to run in parallel and notifies when all are finished.
Example:
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
DispatchQueue.global().async {
sleep(2)
print("Task 1 completed")
dispatchGroup.leave()
}
dispatchGroup.enter()
DispatchQueue.global().async {
sleep(1)
print("Task 2 completed")
dispatchGroup.leave()
}
dispatchGroup.notify(queue: DispatchQueue.main) {
print("All tasks completed!")
}
✅ Expected Output:
Task 2 completed
Task 1 completed
All tasks completed!
🚀 dispatchGroup.notify runs after all tasks finish.
16. How do you implement a Barrier Block in GCD?
✅ Answer:
A barrier block ensures a task executes alone, preventing simultaneous reads and writes.
Example:
let concurrentQueue = DispatchQueue(label: "com.example.barrierQueue", attributes: .concurrent)
concurrentQueue.async {
print("Reading data...")
}
concurrentQueue.async(flags: .barrier) {
print("Writing data safely!")
}
concurrentQueue.async {
print("Reading data again...")
}
✅ Expected Behavior:
- Read operations execute concurrently.
- Write operation (barrier block) executes alone.
17. How do you cancel an ongoing task in GCD?
✅ Answer:
Use DispatchWorkItem to cancel a task before execution.
Example:
let workItem = DispatchWorkItem {
print("Executing work item")
}
DispatchQueue.global().asyncAfter(deadline: .now() + 2, execute: workItem)
workItem.cancel() // ❌ Task won't execute
✅ The task will never run because it’s canceled before execution.
18. How do you implement a Timer using GCD?
✅ Answer:
Use DispatchSourceTimer for accurate timers.
Example:
let timer = DispatchSource.makeTimerSource()
timer.schedule(deadline: .now(), repeating: 1.0)
timer.setEventHandler {
print("Timer fired!")
}
timer.resume()
✅ This prints \”Timer fired!\” every second.
19. What is the difference between DispatchSemaphore and DispatchGroup?
✅ Answer:
| Feature | DispatchSemaphore | DispatchGroup |
|---|---|---|
| Purpose | Controls access to resources | Waits for multiple tasks |
| Blocks Execution? | ✅ Yes (If no resources available) | ❌ No (Asynchronous) |
| Use Case | Limiting concurrent threads | Waiting for multiple async tasks |
Example: Limiting Threads with DispatchSemaphore
let semaphore = DispatchSemaphore(value: 2)
DispatchQueue.global().async {
semaphore.wait()
print("Task 1 executing")
sleep(2)
semaphore.signal()
}
DispatchQueue.global().async {
semaphore.wait()
print("Task 2 executing")
sleep(2)
semaphore.signal()
}
DispatchQueue.global().async {
semaphore.wait() // Will wait until one task finishes
print("Task 3 executing")
semaphore.signal()
}
✅ Limits concurrency to 2 threads.
🚀 Final Takeaways
1️⃣ Main Thread (DispatchQueue.main) handles UI updates.
2️⃣ Global Queue (DispatchQueue.global()) runs background tasks.
3️⃣ Avoid deadlocks by never calling sync inside DispatchQueue.main.
4️⃣ Use DispatchGroup to wait for multiple async tasks.
5️⃣ Use DispatchSemaphore to limit concurrent operations.
6️⃣ Use DispatchWorkItem to cancel a task before execution.
💡 Want more deep-dive Swift concurrency topics? 🚀🔥