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
main
queue 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.main
for 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
sync
on the main thread causes a deadlock! - Use
async
to 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 callingsync
insideasync
does not cause a deadlock.
12. What is the difference between DispatchQueue.global().asyncAfter
and DispatchQueue.main.asyncAfter
?
β Answer:
-
asyncAfter
delays execution without blocking the current thread. -
DispatchQueue.global().asyncAfter
executes a background task after a delay. -
DispatchQueue.main.asyncAfter
executes 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().asyncAfter
for delayed background tasks. - Use
DispatchQueue.main.asyncAfter
for 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? ππ₯