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? ππ₯