Friday, 6 October 2023

GCD — Grand Central Dispatch in iOS

 

Grand Central Dispatch 🚦

Hello everyone! I’m excited to share my knowledge about GCD — Grand Central Dispatch in iOS and how it works in Swift.

# Problem šŸ˜•

Very often we are working with large items that can freeze our apps or hang which is not user-friendly. When we work with the backend and transform or get large data we can do heavy work at the background thread and do not overload the Main Thread.

# Solution šŸ˜Š

The GCD — Grand Central Dispatch gives us many opportunities to control our data flow and manage our threads which is the most important.

Let’s go through the GCD and fully understand what is it.

How does Grand Central Dispatch work 🚦

Grand Central Dispatch, or GCD, is a low-level API designed for managing concurrent operations. It enhances your application’s smoothness, responsiveness, and overall performance. Oftentimes, developers encounter issues such as application hang or freezing when attempting to perform multiple tasks simultaneously. This is a common challenge, and that’s where GCD comes into play, allowing you to efficiently manage multiple tasks concurrently.

I am sure when you work with the network and get an image from the network and pass that image to your UIImageView.image it crashes your app or gives you information that you are not in the Main thread and cannot assign the image to UIImageView.image.

Very often you use this structure code to solve that problem.

DispatchQueue.main.async {
// preform your async code here
// for example
// UIImageView.image = myImageFromNetwork
}

All UIKit elements, such as UIImageView, UILabel, UITextView, and more, are positioned on the Main Thread. When working with network requests, they are not on the Main Thread, necessary the use the code mentioned above to assign images.

The most important thing to know is that GCD manages the tasks FIFO order.

# Concurrent

What does ‘concurrent’ mean? The GCD allows tasks to be executed concurrently on multiple CPU cores, which means it executes multiple tasks at the same time, but it’s not guaranteed that they will finish at the same time. Additionally, they can finish in any order.

Concurrent Queue

# Serial

What does ‘serial’ mean? The GCD typically executes tasks on a single thread of execution. This means that only one task runs at any given moment, and tasks are processed sequentially.

Serial Queue

Queues can be either “serial” or “concurrent”.
Serial Queues guarantee that only one task runs at any given time.
Concurrent queues allow multiple tasks to run at the same time. The queue guarantees tasks start in the order you add them. Tasks can finish in any order.

# Understanding Queue Types 🤫

The GCD provides three main types of queues:
Main queue: Runs on the main thread and is a serial queue.

Global queues: Concurrent queues shared by the whole system. Four such queues exist, each with different priorities: “high”, “default”, “low”, and “background”. The background priority queue has the lowest priority and is throttled in any I/O activity to minimize negative system impact.

Custom queues: Queues you create that can be “serial” or “concurrent”.

# Custom Queues in action šŸ”„

# Serial queue

// Creating a custom serial queue with a specific label
let customQueue = DispatchQueue(label: "com.example.myqueue")

// Adding a task to the custom queue
customQueue.async {
// Perform some work here
}

# Concurrent queue

// Creating a custom concurrent queue with a specific label
let customConcurrentQueue = DispatchQueue(label: "com.example.myconcurrentqueue", attributes: .concurrent)

// Adding tasks to the custom concurrent queue
customConcurrentQueue.async {
// Task 1: Perform some work here
}

customConcurrentQueue.async {
// Task 2: Perform some work here
}

customConcurrentQueue.async {
// Task 3: Perform some work here
}

# About Quality of Service Classes(QoS)

  • User-interactive: This represents tasks that must complete immediately to provide a nice user experience. Use it for UI updates, event handling and small workloads that require low latency. The total amount of work done in this class during the execution of your app should be small. This should run on the main thread.
  • User-initiated: The user initiates these asynchronous tasks from the UI. Use them when the user is waiting for immediate results and for tasks required to continue user interaction. They execute in the high-priority global queue.
  • Utility: This represents long-running tasks, typically with a user-visible progress indicator. Use it for computations, I/O, networking, continuous data feeds and similar tasks. This class is designed to be energy efficient. This gets mapped into the low-priority global queue.
  • Background: This represents tasks the user isn’t directly aware of. Use it for prefetching, maintenance and other tasks that don’t require user interaction and aren’t time-sensitive. This gets mapped into the background priority global queue.
  • Default: The priority level of this QoS falls between user-initiated and utility. This QoS is not intended to be used by developers to classify work. Work that has no QoS information assigned is treated as default, and the GCD global queue runs at this level.
  • Unspecified: This represents the absence of QoS information and cues the system that an environmental QoS should be inferred. Threads can have an unspecified QoS if they use legacy APIs that may opt the thread out of QoS.

Let’s take a look at a few examples.

# Perform background tasks and also update UI.

DispatchQueue.global(qos: .background).async { 
// Call your background task
DispatchQueue.main.async {
// UI Updates here for task complete.
}
}

# DispatchQueue with delay 

let deadlineTime = DispatchTime.now() + .seconds(1)
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
//Perform code here
}

# Sync and Async

func syncExamples() {
let people = DispatchQueue(label: "syncExamples.queue.people")
let age = DispatchQueue(label: "syncExamples.queue.age")

people.sync {
for name in ["Jack", "John", "Macheal"] {
print(name)
}
}

age.sync {
for age in [29, 34, 73] {
print(age)
}
}
}

syncExamples()
func asyncExamples() {
let people = DispatchQueue(label: "syncExamples.queue.people")
let age = DispatchQueue(label: "syncExamples.queue.age")

people.async {
for name in ["Jack", "John", "Macheal"] {
print(name)
}
}

age.async {
for age in [29, 34, 73] {
print(age)
}
}
}

asyncExamples()

If you want to fully understand, you should create your own example and add more print statements to track what happens.

No comments:

Post a Comment