Jul 23, 2022 iOS

Concurrency in iOS swift

Concurrency in iOS swift is the method of running the different task at the same time or in random order. Basically its a multiple task running at same time. Most of the task are run in the different threads. Each task runs at the different thread which runs on the particular interval.

Why Concurrency is used in iOS ?

It is basically critical to understand the app at the user end which the app does not slow down or reduce the performance of the app. Basically the impact is on the scrolling of images or loading the data at the user end. If the user experience the slowness of the app he may uninstall the app. To increase the performance of the app and concurrency comes into picture.

Concurrency is segregated into 2 parts

  1. GCD (Grand Central Dispatch)
  2. Operational Queues

How Concurrency is Achieved ?

  • Time Slicing and Context Switching
  • Interruptions
  • Scheduling Algorithms
  • Latency
  • Time Quantum

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

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:

  1. Run heavy tasks in the global queue.
  2. 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

Let me know if you need more details! 🚀🔥

Synchronous and Asynchronous

Synchronous : Execution of task in synchronous manner. here the task are executed one after the another. Untill and unless the 1st task is completed 2nd task is on hold.

GCD execute the task in the form of synchronous and Asynchronous format. In Dispatch Queue Synchronous the task gets executed in the orderly manner .Every task waits until the initial task gets executed and completes it.

// Do the  work synchronously
DispatchQueue.main.sync { ... }

Asynchronous : Execution of task in parallel order . the task 2 is executed even if task 1 is not finished.

Dispatch Queue Asynchronous the task execution’s takes place in unorder manner .The code is run in asynchronous manner

// Do the work asynchronously
DispatchQueue.main.async { ... }

What is Queue ?

Lining up of the task to get executed it can be form of synchronous or Asynchronous format

There are 2 types of Queue

  1. Serial Queue : Serial queue are the queues which are executed one after the another. Task are executed in orderly manner. here task are executed in sequential manner. Execution of task is one at a time.

Example : Consider a following task . Task 1, Task 2 and Task 3 this task has to be executed in serial queues

Task 1 gets executed first then task 2 and then task 3 . Task 2 does not start before Task 1 is completed so same for Task 3 , Task execution takes place in sequential manner .

Pros of serial Queues

  • Predictable Executable order
  • Prevent Race Condition

2.Concurrent Queue : Task are executed in the same order. The execution of code takes place in parallel format.

Example : Consider a following task. Task 1, Task 2 and Task 3 this task has to be executed in Concurrent queues

Task 1, Task 2 and Task 3 are executed in parallel order . Here Task 2 does not wait for Task 1 to be completed . It execute same order as Task 1 and same for Task 3 all the task are executed in parallel format.

Pros of concurrent Queues

  • Faster
  • Unpredictable order

Serial/Concurrent affects the destination queue to which task is dispatching.

Sync/Async affects the current thread from which task is dispatching

Difference Between sync and serial in GCD (Grand Central Dispatch)

The terms sync (synchronous execution) and serial (serial queue) are related but refer to different concepts in GCD.


1. sync vs async (Execution Type)

These control how a task is executed:

Execution Type Description
sync (Synchronous) Waits for the task to finish before continuing. Blocks the current thread.
async (Asynchronous) Starts the task but does not wait for it to finish. Continues execution immediately.

Example: sync Blocks Execution

let queue = DispatchQueue.global()

queue.sync {
    print("Task 1 started")
    sleep(2)
    print("Task 1 completed")
}

print("Task 2 started") // This waits until Task 1 completes

Output (Always in order)

Task 1 started
Task 1 completed
Task 2 started

Example: async Does Not Block

queue.async {
    print("Task 1 started")
    sleep(2)
    print("Task 1 completed")
}

print("Task 2 started") // This runs immediately

Possible Output (Order may vary)

Task 2 started
Task 1 started
Task 1 completed

2. serial vs concurrent (Queue Type)

These control where the task runs:

Queue Type Description
serial queue Executes one task at a time, in order.
concurrent queue Executes multiple tasks at the same time.

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")
    sleep(1)
    print("Task 2 completed")
}

Output (Always in order)

Task 1 started
Task 1 completed
Task 2 started
Task 2 completed

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")
    sleep(1)
    print("Task 2 completed")
}

Possible Output (Order may vary)

Task 1 started
Task 2 started
Task 2 completed
Task 1 completed

3. sync with a Serial Queue (Deadlock Risk)

  • Calling sync on the same serial queue causes a deadlock because the queue is waiting for itself to finish.
let serialQueue = DispatchQueue(label: "com.example.serialQueue")

serialQueue.sync {
    print("Task 1 started")

    serialQueue.sync {  // ❌ Deadlock (Waiting on itself)
        print("Task 2 started")
    }

    print("Task 1 completed")
}

This freezes execution because Task 1 is waiting for Task 2, but Task 2 can’t start until Task 1 finishes.

Fix: Use async inside sync instead.


4. sync with a Concurrent Queue

let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)

concurrentQueue.sync {
    print("Task 1 started")
    sleep(2)
    print("Task 1 completed")
}

print("Task 2 started") // Runs after Task 1 finishes

Output (Always in order)

Task 1 started
Task 1 completed
Task 2 started

Final Summary

Feature sync async serial queue concurrent queue
Blocks execution? ✅ Yes ❌ No ✅ Yes (One at a time) ❌ No (Multiple tasks at once)
Executes in order? ✅ Yes ❌ No ✅ Yes ❌ No
Can cause deadlocks? ✅ Yes ❌ No ✅ Yes (if sync calls itself) ❌ No
Use case When you need order & dependency When you need performance When you need thread safety When you need parallel execution

🚀 When to Use What?

Use sync when you need to wait for a task to complete (but avoid inside the same queue).
Use async for long-running tasks, so they don’t block execution.
Use Serial Queue when tasks depend on each other or need thread safety.
Use Concurrent Queue for independent tasks that can run in parallel.

Let me know if you need more examples! 😊🔥

There are 2 different types of Dispatch Queue

  1. Main Queue
  2. Global Queue

Main Queue : This is used to execute the task on main thread .this thread mainly used as UI Thread

DispatchQueue.main.async {
     // Perform your async code here
}

Global Queue: This is used to execute the task on the global thread

DispatchQueue.global().async {
     // Perform your async code here
}

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
Index