Reader small image

You're reading from  An iOS Developer's Guide to SwiftUI

Product typeBook
Published inMay 2024
Reading LevelBeginner
PublisherPackt
ISBN-139781801813624
Edition1st Edition
Languages
Right arrow
Author (1)
Michele Fadda
Michele Fadda
author image
Michele Fadda

Michele is currently working as a technical project and program manager at Eggon, an innovative startup in Padua, Italy, and as the director of FWLAB Limited, a UK App development studio. He specializes in skills such as Imperative Programming, Functional programming, Swift, Mobile Application Development. He started programming as a child with Assembly language and has worked for 20 years as a consultant using a variety of languages and technologies. He started developing for iOS with iOS on iOS v.3.0 in 2009. He has also developed many apps as a solo developer and has also participated in numerous projects.
Read more about Michele Fadda

Right arrow

Modern Structured Concurrency

Concurrency does not directly impact SwiftUI, but it is unavoidable as a concept in writing any application that is non-trivial, and as such, I think it is important enough a subject to deserve at least a chapter in this book. Concurrency is a concept that, over time, has become increasingly important in the development of mobile applications. Concurrency is the ability of a system to manage multiple tasks or operations at the same time. With the escalation in task complexity and the need for faster, more efficient processing to provide users with the best experience, the capacity to execute multiple operations concurrently has become progressively more crucial. Historically, programming languages have provided various mechanisms to manage concurrent execution, such as threads, callbacks, and event loops. These approaches, while functional, often lead to writing complicated and error-prone code. Apple has attempted several times to simplify concurrent...

A brief introduction to concurrency

Concurrency in the context of iOS development with Swift is about managing multiple tasks that an application might need to perform simultaneously, such as fetching data from the internet, while keeping the user interface responsive by avoiding blocking the main thread, which is responsible for UI updates.

In this chapter, we will focus on the most recent approaches.

Parallelism, while related to concurrency, is specifically about executing multiple tasks at exactly the same time, particularly on devices with multi-core processors, and this includes all modern mobile devices and computers in general. iOS devices leverage parallelism to perform complex computations or handle multiple operations at once, enhancing performance. Swift and iOS provide several mechanisms to facilitate the parallel execution of tasks.

Race conditions in Swift development become a concern when multiple tasks try to access or modify the same piece of data concurrently...

Understanding traditional concurrency mechanisms

My recommendation is that, although old concurrency paradigms can be still used, you should not mix concurrency paradigms together, and you should use the newer approaches in new projects. However, let’s have a quick recap of older concurrency mechanisms.

Threads

A thread in concurrent programming is the smallest unit of a CPU execution that can run independently. Threads within the same process share the same memory space but execute independently. Each thread owns its own copy of registers, stack and program counter, but shares heap memory and file handles. Threads are one of the oldest and most traditional ways to achieve concurrency. A modern CPU can both switch the execution sequentially among several threads (concurrency) and can also process several threads in parallel on different CPU cores (parallelism).

It is nowadays uncommon to find a modern mobile device with less than four CPU cores. In a threaded model...

What is structured concurrency?

Structured concurrency is a programming paradigm that aims to make concurrent programming as straightforward, safe, and simple to understand as writing sequential code. It takes its name as an analogy stemming from structured programming.

The basic assumption of structured concurrency is that it should bring the same simplification to concurrency that structured programming brought to program sequencing, by removing goto-based branching.

Every asynchronous task is now part of a hierarchy and has a priority. The hierarchy allows the runtime to cancel all the child tasks of a parent task when the parent is canceled. It also simplifies waiting for all children to finish before the parent is allowed to complete. Also, the compiler checks whether code can or should be run asynchronously. Although not perfect, the compile-time check catches the most common programming errors. It will, for instance, detect some race conditions and potentially unsafe code...

Using the async/await syntax

The async/await syntax is the backbone of this structured concurrency. Let’s begin by understanding the async keyword.

async in Swift means that a function will perform some asynchronous operation, suspending the thread till the operation completes, and returning only when that operation is complete. Unlike a standard function, a function marked with the async keyword doesn’t block the caller but, rather, allows it to continue executing other tasks.

This is useful to perform IO-bound tasks, computationally intensive calculations, networking calls, and file operations, as it won’t stop the CPU while waiting – for instance, while waiting for data from a network operation.

An example of a Swift async function, illustrating the async syntax, is the following example code fragment:

// an example async function
import Foundation
func fetchData() async -> Data {
    // perform a network call
 ...

Understanding tasks

Tasks are “units of work” that can run concurrently, and they are a higher-level abstraction over threads, queues, and similar concurrency primitives. Essentially, in Swift, a task is simply a piece of work that can run in parallel with other tasks. In contrast to threads and queues, tasks are managed by the Swift runtime and not by the operating system. This makes them more lightweight and efficient. Moreover, this allows structured concurrency to be backported by Apple to iOS 13 just by updating Xcode, as iOS 13 did not offer this feature when it was first introduced.

Beware that in the first iterations of structured concurrency, you were supposed to be able to declare a task just by using the async keyword, followed by do/catch. The async keyword followed by do/catch syntax method has been replaced with task initialization. You won’t find that in this book; if you happen to find it in old code, just replace async with an appropriate task...

Asynchronous streams

Asynchronous streams are similar to asynchronous sequences. AsyncStream is the Swift protocol that defines asynchronous streams. It is less flexible, simpler to use, and requires less code than AsyncSequence.

The AsyncStream protocol conforms to AsyncSequence, but it offers a more streamlined method for generating asynchronous sequences and does not require you to define manually an asynchronous iterator.

AsyncStream’s initializer is defined as follows:

init(Element.Type, bufferingPolicy: AsyncStream<Element>.Continuation.BufferingPolicy, (AsyncStream<Element>.Continuation) -> Void)

The initializer builds an asynchronous stream for an element type, with the specified buffering policy and the element-producing closure “continuation.”

Continuations in computer science are abstract representations of the control state of a computer program. A continuation captures the computation that remains to be executed at a certain...

Actors

Actors are a reference type, similar to a class. However, actors protect their internal state by enforcing a single thread that is able to access them at any given time. This is achieved by making sure that each one of the actors’ methods and properties can only be accessed in a strictly sequential serial order. This determines data isolation, making it very hard to write code that could determine common concurrency problems, such as data races.

In structured concurrency, Actors should be used to perform synchronization. You should avoid locks, mutexes, and semaphores, as these are much more likely to introduce race conditions, and reasoning about their behavior is more difficult.

In Swift, actor isolation is enforced at the compile level, meaning that the compiler does not allow you to access the mutable state of the actor from outside the actor.

Swift actors are reentrant, meaning that while an actor awaits the result of an asynchronous operation, it can process...

MainActor

MainActor is a special actor that is tied to the system level and is always present in a modern Apple app, that is based on a version of the operating system that is capable of supporting structured concurrency.

MainActor is tied to the main queue. Any update to the UI in Apple systems needs to be performed within the main queue.

Any code marked with @MainActor will execute on the MainActor, which is tied to the main queue, and this is particularly useful to ensure that UI update-related code is not executed on threads different from the main one.

For instance, the code shown in the following fragment would be executed in MainActor:

@MainActor
private func showAlert() {
        isAlertShown = true
}

And this would be needed if we wanted to be able to, for example, invoke the showAlert() function from a within background task, but with the need to affect the UI.

@MainActor can be used with closures, classes, structs...

Bridging old GCD and structured concurrency

In general, if you can, you should avoid mixing concurrency paradigms. However, sometimes, you have old code that uses GCD and you want to use it, and maybe you can’t or don’t want to modify the old asynchronous code you need to invoke, adapting it to the more modern structured concurrency approach, because it may be part of a library, and you may not have access to the library source code.

withCheckedThrowingContinuation is a Swift function that bridges between asynchronous code and traditional completion handler-based code. It’s particularly useful when you have an API that uses completion handlers and you want to use it within Swift’s modern async/await-structured concurrency model.

The withCheckedThrowingContinuation function takes as an argument a closure that itself takes a single argument – a continuation. This continuation can be resumed exactly once, either by returning a value with resume...

Summary

In this chapter, we examined structured concurrency in Swift and explained why this new modern approach is preferrable to the old ones. We examined the basic components of structured concurrency, including async/await, async let, tasks, task groups, and asynchronous sequences and streams. You were introduced to the preferred way to synchronize tasks, actors, including the MainActor and user-defined global actors. Finally, we learned how to use the new structured approach with old code that uses old concurrency models.

In the next chapter, we will introduce the SwiftData framework.

lock icon
The rest of the chapter is locked
You have been reading a chapter from
An iOS Developer's Guide to SwiftUI
Published in: May 2024Publisher: PacktISBN-13: 9781801813624
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €14.99/month. Cancel anytime

Author (1)

author image
Michele Fadda

Michele is currently working as a technical project and program manager at Eggon, an innovative startup in Padua, Italy, and as the director of FWLAB Limited, a UK App development studio. He specializes in skills such as Imperative Programming, Functional programming, Swift, Mobile Application Development. He started programming as a child with Assembly language and has worked for 20 years as a consultant using a variety of languages and technologies. He started developing for iOS with iOS on iOS v.3.0 in 2009. He has also developed many apps as a solo developer and has also participated in numerous projects.
Read more about Michele Fadda