Modern Concurrency in Swift

Basic Concepts

Asya Tealdi
6 min readMar 26, 2022

Today we’ll dive into the basics of modern concurrency in Swift, but first let’s understand why you need to know about this topic and in what way you’ll be using it to improve your projects.

Synchronous code / Asynchronous code

What is the difference?
Synchronous programming means that operations are performed one at a time, each task have to wait that the previous one has been completed to start.

For instance, think about the process of cooking pasta with tomato sauce, you boil the water, cook the pasta until it’s ready and then, you start to prepare the sauce(which normally should take a lot of time), meanwhile the pasta has cooled and you end up eating a very sad dish. Do you think this was the best way to prepare it ? Don’t you think there’s another way to not waste so much time and resources ?

Asynchronous code can be suspended and resumed later, although only one task executes at a time. This is a lot useful when you are waiting for a response from the server (or a third-party API) and you are able to start another task while waiting the result, but in our case it doesn’t make sense to stop cooking the pasta, heat the tomato sauce, stop it and finish the pasta.

So what’s the solution?

Concurrency

Writing asynchronous code has always been a challenging task for developers, Apple has provided different tools such as GCD(Grand Central Dispatch), Operation and dispatch queues to help them but in WWDC21 Apple introduced a new concurrency model featuring async/await syntax, that is simple to write, easy to understand and free from data races.
Concurrency is the execution of the multiple instruction sequences at the same time, simply put is the combination of asynchronous and parallel code (multiple pieces of code run simultaneously).
This would pretty much solve all our issues since we are able to perform different task at the same time, in our case cooking the pasta and making the tomato sauce, which can save us lot of time. In this article we’re going to explore the concepts of async/await, tasks and actors.

Async/Await

Async stands for asynchronous and it’s a keyword to say that this particular method execute asynchronous work, which means that the function can pause in the middle while it’s waiting for data.

How to create and call an asynchronous function?
To mark a method use the async right after its parameters and before its return type as shown below:

This method is marked as async throws, which means that it can potentially fail his asynchronous job and throw an error if something went wrong.
When calling an asynchronous function you have to use awaitin front of the call to mark every possible suspension point.

Network requests can take a relatively long time to complete, asynclets the rest of the app’s code keep running while this code waits for the data to be ready, this mechanism is called yielding the thread because Swift suspend the execution of the code on the current thread and runs some other code in that thread instead. You may be wondering what is a thread?

A thread is nothing more than a context in which commands are executed, every program launches with at least one thread where its work takes place, called main thread, and it will always exists for the lifetime of the app, apart from it the application can have many more threads to execute all sorts of other jobs. This is important because all the user interface work and update should take place in the main thread, otherwise you’ll pretty much find that nothing happens or even worse it can lead to crashes.

Asynchronous Sequences

Async sequences are an approach where you iterate over a collection and wait for one element of it at a time instead of all the entire array at once, the execution will suspend at the beginning of each iteration thanks to await:

Asynchronous function in parallel

Let’s suppose you want to fetch a user score history of a game, using await will only make the code run sequentially, every operation will execute after the previous one ended:

Although it is not important for them to be completed in order, each score can be fetched independently or even at the same time. How to do so ?
To call an async function and run it in parallel with other piece of codes just write async in front of let when defining a constant, and then write awaiteach time you use the constant:

It is possible to mix both approaches depending on the kind of work you have to perform.

Task

Using async/await in Swift make us to write asynchronous code in a simple way, but by itself it doesn’t enable us to run effective concurrency. That’s why Swift provides us with 2 particular types: Task and Task group. A task is a unit of work that can be run asynchronously as part of your program. Each task in a task group has the same parent task and each task can have child tasks, this code organization is called structured concurrency which aim to improve the clarity, quality and development time of a computer program.
So, how do you create a Task?

Simple as that, but why using tasks? Tasks allow us to create a concurrent environment from a non-concurrent function, like in the onAppear{ ... } method below which, otherwise, would not compile:

The code above fetches an image and displays it accordingly if the request succeeds. Tasks will execute without any reference to it as soon as you create them, the only reason to keep a reference is to give yourself the ability to wait for a result or cancel the task.
You can use tasks to produce either a value or an error:

Actors

Actors are reference types and work similarly to classes, but they make sure that only one task can mutate the actor’s state at a time. What does this mean?
For istance, you are at the shopping mall and want to buy a new jacket, you’re heading for the checkout and will pay with a shared credit card with your sister, meanwhile she buys another type of product somewhere else at the same time, normally, this would lead to a mistake in the account since they are both trying to access and modify a property simultaneously, resulting in a miscalculation of the balance.
Actors prevent this problem and makes it safe for code in multiple task to interact with them sequentially. If you try to access those properties from outside the actor, like you would with an istance of a class, you will get errors, accessing without writing the await keyword fails because the properties of an actor are part of that actor’s isolated local state (also known as actor isolation).

How to create an actor?

The initializer syntax is the same as structure and classes. When accessing a property or a method of an actor, use await to mark the potential suspension point:

So, I hope this all makes sense to you, this article aimed to introduce the basic concepts of modern concurrency without going too much in details and overwhelm you, but once you understand the logic behind I encourage you to explore more detailed resources on these topics which are fundamental for your developer career.

--

--

Asya Tealdi

Alumni @Apple Developer Academy and long-term learner. I am passionate about iOS development and its technologies.