At WWDC21, Apple introduced Swift 5.5, which can be used for testing. Among its new features, the most anticipated is better concurrency support with aySNC /await and actor.

Apple says the asynchronous feature Q is designed to make concurrent Swift code easier to write and understand. Traditionally, Swift has used closures and completion handlers to handle asynchronous operations. It is well known that this approach can quickly lead to “callback hell” when your code has many asynchronous operations, or when the flow of control is complex.

Swift’s asynchronous functions bring looping lines to the language.

Functions can choose to be asynchronous, allowing programmers to compose complex logic involving asynchronous operations using normal control flow mechanisms. The compiler is responsible for translating an asynchronous function into the appropriate set of closures and state machines.

The following code snippet shows how to declare and invoke async as if they were synchronous.

func loadWebResource(_ path: String) async throws -> Resource
func decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Image
func dewarpAndCleanupImage(_ i : Image) async throws -> Image

func processImageData() async throws -> Image {
  let dataResource  = try await loadWebResource("dataprofile.txt")
  let imageResource = try await loadWebResource("imagedata.dat")
  let imageTmp      = try await decodeImage(dataResource, imageResource)
  let imageResult   = try await dewarpAndCleanupImage(imageTmp)
  return imageResult
}
Copy the code

While asynchronous functions seem to greatly simplify concurrency management, they do not rule out the possibility of deadlocks or state corruption. In particular, programmers should be aware of the problems posed by pause-point asynchronous functions. At a pause point, a function abandons its thread. This can happen, for example, when you call an asynchronous function that is related to a different execution context. To avoid the risk of deadlocks or data corruption, asynchronous functions should avoid calling functions that might block their thread.

For example, acquiring a MUtex can only block until one of the currently running threads abandons the mutex; This is sometimes acceptable, but must be used carefully to avoid introducing deadlocks or artificial scalability issues. Instead, waiting for a condition variable can block until some arbitrary other work is scheduled as a signal variable; This model strongly opposes recommendations.

An interesting evolution of this functionality makes it possible to call asynchronous Objective-C apis that use completion handlers and use await expressions.

Actors, on the other hand, are abstractions built on async and await and can safely access mutable states. In short, an actor encapsulates some state and provides a set of methods to access it securely.

Unlike a class, an actor allows only one task at a time to access its mutable state, making it safe for code from multiple tasks to interact with the same instance of the actor.

This is an example of a Swift actor.

actor TemperatureLogger {
    let label: String
    var measurements: [Int]
    private(set) var max: Int

    init(label: String, measurement: Int) {
        self.label = label
        self.measurements = [measurement]
        self.max = measurement
    }
}
Copy the code

An actor’s methods can be used synchronously or asynchronously from within the actor, but the compiler forces you to use asynchronous operations to read the actor’s state from outside the actor.

If you’re interested in learning how Swift concurrency works behind the scenes, learning the difference between Swift tasks and Grand Central Dispatch, and how to code Swift concurrency with performance in mind, don’t miss Swift concurrency at apple’s WWDC conference. Behind the scenes.

Swift 5.5 is currently available as part of the Xcode 13 beta and can be downloaded from the Apple Developer website.