iOS Concurrency in a Nutshell (Ep-I)

Concurrency Basics, Grand Central Dispatch (GCD), Synchronous, Asynchronous, Serial Queue, Concurrent Queue, Main Queue, Concurrency Examples

Emin DENİZ
Orion Innovation techClub

--

What is Concurrency?

According to Wikipedia; in computer science, concurrency is the ability of different parts or units of a program, algorithm, or problem to be executed out-of-order or in partial order, without affecting the outcome.

In short words, concurrency allows us to execute different tasks at the same time, or it appears to us at the same time. I know you were confused a little, so let’s get into detail. CPU has a clock mechanism that allows it to slice times. CPU can execute a new task in the new time slice before the previous execution is completed.

Demonstration of Context Switching

These two concepts are called Time Slicing and Context Switching and these 2 concept is the fundamental technics that allows concurrency. Context Switching is happening very frequently it creates an illusion that multiple tasks execute simultaneously. But in reality, tasks are not executed at the same time. Let’s make it visual. For example, let’s assume that we have a CPU with a 10 ms (milliseconds) time slice, and we have 3 tasks to execute. Assume that, task1 takes 8 ms, task2 takes 20 ms and task3 takes 25 ms. Here is a sample demonstration.

Context Switching in Time Slices
  1. At t=0, we have 3 tasks in the queue. None of them executed yet. We have 3 tasks in the queue.
  2. At t=10 ms, the CPU executed the first task and its execution is completed. Because Task 1 takes 8 ms we completed its execution so far and we can pop it from the queue. We have 2 tasks in the queue now.
  3. At t=20 ms, the CPU started to execute Task 2. Task 2 takes 20 ms to execute, so at the end of the 10 ms period, we haven’t completed Task 2. We still have 2 tasks in the queue because Task 2 hasn’t been completed yet.
  4. At t=30 ms, the CPU started to execute Task 3. So even if the previous task hasn’t been completed our context switched to the next task. Task 3 takes 25 ms to execute, so at the end of the 10 ms period, we haven’t completed Task 3. We still have 2 tasks in the queue.
  5. At t=40 ms, the CPU continued to execute Task 2. At the end of this cycle execution of Task 2 was completed because we spent 20 ms in total for Task 2. We have 1 task in the queue now
  6. At t=50 ms, the CPU continued to execute Task 3. At the end of this cycle execution of Task 3 hasn’t been completed. We still need to execute it for one more cycle.
  7. At t=60 ms, the CPU continued to execute Task 3. At the end of this cycle execution of Task 3 was completed because we spent over 25ms in total for Task 3. Our queue is empty and we don’t have any more tasks to execute now.

This is how the CPU executes tasks concurrently. Keep in mind that task execution times (8 ms, 20 ms, 25 ms) are given as an example for you to understand this concept better. In reality, the CPU can not know which tasks take how much time. This is the reason why the CPU tries to execute tasks one by one. This way CPU can rid of tasks that take less time quickly.

Concurrency in iOS

Like all modern languages, concurrency in iOS achieving by using multi-thread programming. We have a few iOS framework APIs that allow us to develop multi-thread apps.

Even though my focus is Swift, the multi-thread APIs we will discuss in this article applies to both Swift and Objective-C.

Manual Thread Creation

This is the raw way of implementing multi-thread applications in Swift. With manual thread creation, we will have more control and customization of threads. We can start, cancel, delay threads or change the stack size. We don’t have any abstraction layer. It gives us more control to manage the threads.

More control means more responsibility!

You can go through Apple documents about the threads but in this article, I won’t explain to you how you can use them. Because it is not the recommended way to manage threads in iOS. It gives us more control but more control means more responsibility. You have to deal with a lot of problems with manual threads.

  • When the system conditions change we have to handle each thread. There is no abstract layer to manage the threads. We have to deal with each of those conditions.
  • We had to deallocate when they finishes.
  • If we can’t manage them properly, we may cause memory leaks.
  • Auto release pool won’t manage the threads we created.
  • We had to maintain the order of execution.

The short word is don’t use manual threads if you are not an expert on this. Again, this is not the recommended way to implement multi-thread applications in iOS. We have a better way to manage threads on iOS.

Grand Central Dispatch (GCD)

GCD is a queue-based API that allows executing closures on worker's pools in the FIFO (First in first out) order. After submitting any task, GCD will use its thread pool and execute the tasks using those threads. We will dive into the properties of the GCD but let me show you the API.

// Some code
var results: [Result]?

DispatchQueue.main.async {
items = fetchResults(name: "Weathers")
}

func fetchResults(name:String) -> [Result] {
// Run some logic
}

In the sentence above I mentioned GCD executing tasks in FIFO order. To achieve this GCD can have a lot of queues (Task Queues). DispatchQueue is the API that manages those queues. A dispatch queue executes tasks either serially or concurrently (We will get into details). As in the example code above, we are managing the threads with DispatchQueues. We are submitting a logic to the DispatchQueue and it executes the logic. At the end of the execution, we are receiving the result in the same block. It is that easy!

Under the hood, GCD has Task Queues that stores tasks in FIFO order. When there is a task in the queue and there is an available thread in Thread Pool GCD starts to execute it. After the task is completed GCD moves it to Completed Tasks and informs the block about the execution completion. The demonstration below shows this flow visually.

Grand Central Dispatch (GCD) Task Execution Demonstration

As a developer, we are just submitting a task to GCD and expect it to take care of everything and then inform us of completion. We don’t care how many threads we have, which one is available, which thread should execute our task etc. It is as abstract as we want.

Preserving Data Consistent

When you research a little about multi-thread programming you will see a few famous problems like Deadlock or Race Conditions. All of those problems can be categorized as Data Inconsistency Problems. This is a challenge we must solve as developers. GCD gives us the ability to modify the executions in our desired order and manner. With that ability, we can solve these problems. Let’s see those titles one by one.

Order of Execution (Serial vs Concurrent)

We already talked about GCD having Task Queues. Those queues have different orders of execution styles. Depending on our needs we can choose the suitable type for us.

Serial Queue

A Serial Queue is a queue type that executes one task at a time. If we submit multiple tasks to a serial queue, these tasks will be executed serially one by one.

Serial Queue Task Execution Demonstration

Serial Queues are handy if we need to wait for the next task execution before the previous task is completed. For example, we might have one task to ‘modify data in DB’ and another task to ‘read data from the DB’. If we use Serial Queue for this purpose, we will ensure that the data we read will be persistent. Because we will read the data without any modification occurring during the reading time.

Concurrent Queue

A Concurrent Queue is a queue type that executes multiple tasks at a time. If we submit multiple tasks to a serial queue, these tasks will be executed concurrently.

Concurrent Queue Task Execution Demonstration

Concurrent Queues are handy if we don’t need to wait for the next task execution before the previous task is completed. For example, we can use a concurrent queue to perform tasks such as loading data from a server and processing it, or rendering images and displaying them on the screen.

Manner of Execution (Synchronous vs Asynchronous)

In Swift (like any other programming language), we may prefer blocking the current thread until some execution is completed. Or we may prefer not to block the current thread and execute some tasks simultaneously or later on. This is the manner of execution that we are talking about.

Synchronous

In case we want to block (do not allow anything to execute anything else) the current thread until some execution is completed we need to use Synchronous execution. This is very handy if we need to want to block execution on a queue.

Asynchronous

In case we don’t want to block the current thread and execute some tasks simultaneously or later on, we need to use Asynchronous execution. When we submit an asynchronous task to a dispatch queue, the task is added to the queue and then executed at some point in the future, when system resources are available. The execution order is decided by the system.

In case we use asynchronous, the program exits from the block immediately.

Main Queue

If we don’t define any queue at all iOS runs our code in the Main Queue. Main Queue is a serial queue that executes tasks synchronously if we don’t specify otherwise. It is the main thread that executes the task when the main program started. If you haven’t defined any dispatch queue in your app, tasks will be executed on the main queue. Also, it is important for you to know that this is the queue where you can update the UI. In case we want to update the UI, we have to use the main queue.

Concurrency examples in GCD

Let’s have some examples to understand those concepts better.

Example-1

Let’s have an example of a Single Serial Queue with Multiple Asynchronous Dispatch.

/// Example-1
/// Single Serial Queue with Multiple Asynchronous Dispatch.

let serialQueue = DispatchQueue(label: "aSerialQueue")

serialQueue.async {
print("---- First loop start -----")
for i in 1...5 {
print("Value is \(i)")
}
print("---- First loop end -----")
}

serialQueue.async {
print("---- Second loop start -----")
for i in 6...10 {
print("Value is \(i)")
}
print("---- Second loop end -----")
}

serialQueue.async {
print("---- Third loop start -----")
for i in 11...15 {
print("Value is \(i)")
}
print("---- Third loop end -----")
}

In the code block above we have a single serial queue. We are submitting 3 tasks to that queue in an asynchronous manner. In this example tasks are print statements. You will see the exact same outputs when you run this code block above multiple times. Here is the output.

---- First loop start -----
Value is 1
Value is 2
Value is 3
Value is 4
Value is 5
---- First loop end -----
---- Second loop start -----
Value is 6
Value is 7
Value is 8
Value is 9
Value is 10
---- Second loop end -----
---- Third loop start -----
Value is 11
Value is 12
Value is 13
Value is 14
Value is 15
---- Third loop end -----

These outputs are showing us a few valuable key points.

  • Tasks are executed ordered manner. Dispatch Queue executes the first loop then the second and last the third. Because the queue we have is a serial queue. Even if we use async execution, the serial queue executes tasks in serial order.
  • Executing tasks in a single serial queue same as not using Dispatch Queue at all. You can try to change asynchronously to synchronously in this example, results won’t be changed. Because the queue is serial and executes tasks in serial order.

Example-2

Let’s have an example of a Single Serial Queue with Multiple Asynchronous Dispatch & Synchronous codes.

In this example, we only have the Main Queue. We will talk about the Main Queue later. At this point, I can say that Main Queue is a serial queue that executes tasks. As the name implies it is the main queue. In other words, it is the main thread that executes the task when the main program started. If you haven’t defined any dispatch queue in your app, tasks will be executed on the main queue.

In this example, we didn’t define any queue at the beginning. We will only use the main queue.

/// Example-2
/// Single Serial Queue with Multiple Asynchronous Dispatch & Synchronous codes.

print("👩‍💻👨‍💻Print no:1👩‍💻👨‍💻")
DispatchQueue.main.async {
print("---- First loop start -----")
for i in 1...5 {
print("Value is \(i)")
}
print("---- First loop end -----")
}

print("👩‍💻👨‍💻Print no:2👩‍💻👨‍💻")

DispatchQueue.main.async {
print("---- Second loop start -----")
for i in 6...10 {
print("Value is \(i)")
}
print("---- Second loop end -----")
}

print("👩‍💻👨‍💻Print no:3👩‍💻👨‍💻")

DispatchQueue.main.async {
print("---- Third loop start -----")
for i in 11...15 {
print("Value is \(i)")
}
print("---- Third loop end -----")
}

print("👩‍💻👨‍💻Print no:4👩‍💻👨‍💻")

So there are print statements inside and outside of the blocks. Here are the outputs.

👩‍💻👨‍💻Print no:1👩‍💻👨‍💻
👩‍💻👨‍💻Print no:2👩‍💻👨‍💻
👩‍💻👨‍💻Print no:3👩‍💻👨‍💻
👩‍💻👨‍💻Print no:4👩‍💻👨‍💻
---- First loop start -----
Value is 1
Value is 2
Value is 3
Value is 4
Value is 5
---- First loop end -----
---- Second loop start -----
Value is 6
Value is 7
Value is 8
Value is 9
Value is 10
---- Second loop end -----
---- Third loop start -----
Value is 11
Value is 12
Value is 13
Value is 14
Value is 15
---- Third loop end -----

I just add a single run in this case. Because outputs won’t change regardless of how many times we run this code. Here are the key points.

  • Print statements inside of the blocks are executed later. Because we are using asynchronous blocks and asynchronous immediately exit from the block. They are executed asynchronously whenever the system decides them to execute.
  • Print statements outside of the blocks are executed before. The main queue will execute tasks synchronous manner when we don’t define any dispatch blocks. Remember that synchronous blocks the current thread until the execution is completed. This is the normal behavior when don’t have any concurrency at all.
  • In the same queue, asynchronous blocks always are executed after the synchronous blocks are completed. You can try to copy and paste print("👩‍💻👨‍💻Print no:4👩‍💻👨‍💻") thousands of times at the end of the page. You will see that in each run loops in the blocks won’t start until the last synchronous code is executed.
  • The order of execution will always be the same because we are using a single queue.

Example-3

Let’s have an example of a Single Concurrent Queue with Multiple Asynchronous Dispatch.

/// Example-3
/// Single Concurrent Queue with Multiple Asynchronous Dispatch.

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

concurrentQueue.async {
print("---- First loop start -----")
for i in 1...5 {
print("Value is \(i)")
}
print("---- First loop end -----")
}

concurrentQueue.async {
print("---- Second loop start -----")
for i in 6...10 {
print("Value is \(i)")
}
print("---- Second loop end -----")
}


concurrentQueue.async {
print("---- Third loop start -----")
for i in 11...15 {
print("Value is \(i)")
}
print("---- Third loop end -----")
}

In the code block above we have a single serial queue. We are submitting 3 tasks to that queue in an asynchronous manner. You will see the order of outputs changing when you run this code block above multiple times. Here are the outputs.

// ************************* First Run *************************

---- First loop start -----
---- Second loop start -----
Value is 1
Value is 2
---- Third loop start -----
Value is 3
Value is 4
Value is 6
Value is 5
Value is 7
---- First loop end -----
Value is 8
Value is 9
Value is 10
Value is 11
---- Second loop end -----
Value is 12
Value is 13
Value is 14
Value is 15
---- Third loop end -----

// ************************* Second Run *************************

---- First loop start -----
---- Third loop start -----
---- Second loop start -----
Value is 6
Value is 11
Value is 7
Value is 1
Value is 8
Value is 2
Value is 12
Value is 3
Value is 4
Value is 9
Value is 5
Value is 10
Value is 13
---- First loop end -----
Value is 14
---- Second loop end -----
Value is 15
---- Third loop end -----

// ************************* Third Run *************************

---- First loop start -----
---- Second loop start -----
---- Third loop start -----
Value is 1
Value is 11
Value is 2
Value is 6
Value is 12
Value is 7
Value is 3
Value is 13
Value is 4
Value is 8
Value is 14
Value is 5
Value is 9
---- First loop end -----
Value is 15
Value is 10
---- Third loop end -----
---- Second loop end -----

Here are the key points of the outputs.

  • The order of execution can be changed using Concurrent Queue in an Asynchronous manner on each run. We can not anticipate when each task begins to execute or finish. The system decides the order of execution. For example, in the second run, the third loop starts before the second one. And the third one ends after the second one.
  • The execution order of the same task particles is not changing. For example, 1 is always before 2, 2 is always before 3, 3 is always before 4, and so on.
  • The execution order of the different task particles can be changed. For example, in the third run order is; 1>11>2>6>12>7>3>13>4>8>14>9>5>10>15.
The execution order of the third run in a colorized demonstration.

Example-4

Let’s have an example of a Single Concurrent Queue with Multiple Asynchronous and Synchronous Dispatch.

/// Example-4
/// Single Concurrent Queue with Multiple Asynchronous and Synchronous Dispatch.

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

concurrentQueue.async {
print("---- First loop start -----")
for i in 1...5 {
print("Value is \(i)")
}
print("---- First loop end -----")
}

concurrentQueue.sync {
print("---- Second loop start -----")
for i in 6...10 {
print("Value is \(i)")
}
print("---- Second loop end -----")
}


concurrentQueue.async {
print("---- Third loop start -----")
for i in 11...15 {
print("Value is \(i)")
}
print("---- Third loop end -----")
}

concurrentQueue.async {
print("---- Fourth loop start -----")
for i in 16...20 {
print("Value is \(i)")
}
print("---- Fourth loop end -----")
}

This is the almost same example as the previous one. There is just two difference. In the second block, we use synchronous execution, concurrentQueue.sync and we have one more block to show you the difference better. Here are the outputs

// ************************* First Run *************************
---- First loop start -----
---- Second loop start -----
Value is 1
Value is 2
Value is 6
Value is 3
Value is 7
Value is 8
Value is 9
Value is 4
Value is 10
Value is 5
---- Second loop end -----
---- First loop end -----
---- Fourth loop start -----
Value is 16
Value is 17
Value is 18
Value is 19
Value is 20
---- Fourth loop end -----
---- Third loop start -----
Value is 11
Value is 12
Value is 13
Value is 14
Value is 15
---- Third loop end -----

// ************************* Second Run *************************

---- First loop start -----
---- Second loop start -----
Value is 6
Value is 1
Value is 7
Value is 2
Value is 8
Value is 3
Value is 9
Value is 4
Value is 10
Value is 5
---- Second loop end -----
---- First loop end -----
---- Third loop start -----
---- Fourth loop start -----
Value is 16
Value is 11
Value is 17
Value is 12
Value is 13
Value is 14
Value is 15
---- Third loop end -----
Value is 18
Value is 19
Value is 20
---- Fourth loop end -----

// ************************* Third Run *************************

---- First loop start -----
---- Second loop start -----
Value is 6
Value is 1
Value is 7
Value is 8
Value is 9
Value is 2
Value is 10
Value is 3
---- Second loop end -----
Value is 4
Value is 5
---- First loop end -----
---- Third loop start -----
Value is 11
---- Fourth loop start -----
Value is 12
Value is 13
Value is 14
Value is 15
---- Third loop end -----
Value is 16
Value is 17
Value is 18
Value is 19
Value is 20
---- Fourth loop end -----

Here are the key points of the outputs.

  • The execution order of the first and second loops is mixed like in the previous example. The second loop can start before the first one end.
  • The execution order of the third and fourth loops is mixed like in the previous example. The fourth loop can start before the third one end.
  • But the third and fourth loop always starts after the second one completes. Because the second loop executes on a Synchronous block. None of the other blocks in the same queue can start execution until the execution of the second loop is completed.
  • The execution order of the different task particles can be changed but synchronous will force the upcoming tasks to wait. For example, in the second run order is 6>1>7>2>8>3>9>4>10>5>16>11>17>12>13>14>15>18>19>20. In the image below you can see that the output of loops 3 and 4 comes after loop 2.
The execution order of the second run in a colorized demonstration.

Example-5

Let’s have an example of a Serial Queue alongside a Concurrent Queue with Multiple Asynchronous Dispatch.

/// Example-5
/// A Serial Queue alongside a Concurrent Queue with Multiple Asynchronous Dispatch.

// In defaults queues are serial.
let serialQueue = DispatchQueue(label: "aSerialQueue")
// In case we add the concurrent attribute they become concurrent.
let concurrentQueue = DispatchQueue(label: "aConcurrentQueue",
attributes: .concurrent)

serialQueue.async {
print("Serial-First")
}

serialQueue.async {
print("Serial-Second")
}

serialQueue.async {
print("Serial-Third")
}

concurrentQueue.async {
print("Concurrent-Fourth")
}

concurrentQueue.async {
print("Concurrent-Fifth")
}

concurrentQueue.async {
print("Concurrent-Sixth")
}

We are beginning to merge concepts in the examples, that's why I like to start with a simpler example. In the code block above we have 2 queues, one of the is serial and the other is concurrent. We are submitting 3 tasks to each of those queues in an asynchronous manner. In this example tasks are print statements. When you run this code block above you will likely see different outputs every run. For example here are the outputs I saw;

// ************************* First Run *************************
Serial-First
Concurrent-Fourth
Concurrent-Sixth
Concurrent-Fifth
Serial-Second
Serial-Third

// ************************* Second Run *************************
Serial-First
Concurrent-Sixth
Concurrent-Fifth
Concurrent-Fourth
Serial-Second
Serial-Third

// ************************* Third Run *************************
Serial-First
Concurrent-Fourth
Concurrent-Fifth
Concurrent-Sixth
Serial-Second
Serial-Third

// ************************* Fourth Run *************************
Concurrent-Fourth
Concurrent-Sixth
Serial-First
Concurrent-Fifth
Serial-Second
Serial-Third

These outputs are showing us a few valuable key points.

  • Asynchronous and Concurrent queues do not guarantee the order in which blocks will be executed. The outputs are showing us that the execution order of concurrent queues is changing. For example in the first run, Concurrent-Sixth was printed before Concurrent-Fifth.
  • Asynchronous and Serial queues won’t execute the next block until the previous one is completed. The outputs are showing us the order of task execution in the serial queue won’t change even though concurrent queues can start at any time. Order will always be Serial-First, Serial-Second, and Serial-Third.
  • The execution order of the tasks that are submitted to different asynchronous queues can not be anticipated. The outputs show that print statements are changing in each execution. Even we can see the first output as Concurrent-Fourth in the fourth run.

Example-6

This is the same example as the previous one. I just added loops again.

let serialQueue = DispatchQueue(label: "aSerialQueue")
let concurrentQueue = DispatchQueue(label: "aConcurrentQueue",
attributes: .concurrent)

serialQueue.async {
print("---- First loop start -----")
for i in 0...5 {
print("Value is \(i)")
}
print("---- First loop end -----")
}

serialQueue.async {
print("---- Second loop start -----")
for i in 6...10 {
print("Value is \(i)")
}
print("---- Second loop end -----")
}

serialQueue.async {
print("---- Third loop start -----")
for i in 11...15 {
print("Value is \(i)")
}
print("---- Third loop end -----")
}

concurrentQueue.async {
print("---- Fourth loop start -----")
for i in 16...20 {
print("Value is \(i)")
}
print("---- Fourth loop end -----")
}

concurrentQueue.async {
print("---- Fifth loop start -----")
for i in 21...25 {
print("Value is \(i)")
}
print("---- Fifth loop end -----")
}


concurrentQueue.async {
print("---- Sixth loop start -----")
for i in 26...30 {
print("Value is \(i)")
}
print("---- Sixth loop end -----")
}

Here are the outputs I saw;


// ************************* First Run *************************

---- Sixth loop start -----
Value is 26
Value is 27
Value is 28
Value is 29
Value is 30
---- Sixth loop end -----
---- Fourth loop start -----
Value is 16
Value is 17
Value is 18
Value is 19
Value is 20
---- Fourth loop end -----
---- Fifth loop start -----
Value is 21
Value is 22
Value is 23
Value is 24
Value is 25
---- Fifth loop end -----
---- First loop start -----
Value is 0
Value is 1
Value is 2
Value is 3
Value is 4
Value is 5
---- First loop end -----
---- Second loop start -----
Value is 6
Value is 7
Value is 8
Value is 9
Value is 10
---- Second loop end -----
---- Third loop start -----
Value is 11
Value is 12
Value is 13
Value is 14
Value is 15
---- Third loop end -----

// ************************* Second Run *************************

---- First loop start -----
---- Fifth loop start -----
---- Fourth loop start -----
---- Sixth loop start -----
Value is 26
Value is 21
Value is 27
Value is 22
Value is 28
Value is 23
Value is 29
Value is 24
Value is 30
Value is 25
---- Sixth loop end -----
---- Fifth loop end -----
Value is 16
Value is 17
Value is 18
Value is 19
Value is 20
---- Fourth loop end -----
Value is 0
Value is 1
Value is 2
Value is 3
Value is 4
Value is 5
---- First loop end -----
---- Second loop start -----
Value is 6
Value is 7
Value is 8
Value is 9
Value is 10
---- Second loop end -----
---- Third loop start -----
Value is 11
Value is 12
Value is 13
Value is 14
Value is 15
---- Third loop end -----

// ************************* Third Run *************************

---- First loop start -----
---- Fourth loop start -----
---- Fifth loop start -----
---- Sixth loop start -----
Value is 16
Value is 17
Value is 18
Value is 19
Value is 20
---- Fourth loop end -----
Value is 21
Value is 22
Value is 23
Value is 24
Value is 25
---- Fifth loop end -----
Value is 26
Value is 27
Value is 28
Value is 29
Value is 30
---- Sixth loop end -----
Value is 0
Value is 1
Value is 2
Value is 3
Value is 4
Value is 5
---- First loop end -----
---- Second loop start -----
Value is 6
Value is 7
Value is 8
Value is 9
Value is 10
---- Second loop end -----
---- Third loop start -----
Value is 11
Value is 12
Value is 13
Value is 14
Value is 15
---- Third loop end -----

These outputs are showing us a few valuable key points. Keep in mind that the first 3 loops are submitted to a serial queue, other 3 loops are submitted to a concurrent queue. So that means if we saw a value between 0–15 it belongs to the serial queue, and if we saw a value between 16–30 it belongs to the concurrent queue.

  • Asynchronous and Concurrent queues do not guarantee the order in which blocks will be executed. In each execution Values between 16–30 are mixed up. For example at the beginning of the second run values in loop 5 and loop 6 mixed up.
  • Asynchronous and Serial queues won’t execute the next block until the previous one is completed. In each execution orders are preserved. 6 will never be printed before 5 and 11 will never be printed before 10.
  • The execution order of the tasks that are submitted to different asynchronous queues can not be anticipated. In each execution, the printed number order is changed.

Summary

There are a lot of topics that we need to discuss the concurrency in iOS. But I believe we have a good start on the fundamentals in this article. Without concurrency, it is impossible to have an application with a good user experience. All iOS developers should know about the concurrency concepts to implement better applications. I am planning to have a few articles in this series. I am planning to talk about all the important topics of concurrency in this series.

I encourage you to play with the topics we talked about in this article. Here is the link to the repository that contains examples in this article.

Take care till we meet again!

--

--