Modern Concurrency: Beyond the Basics
Build on your knowledge of async/await, tasks and asynchronous sequences to use concurrent tasks in safe, performant and predictable asynchronous apps. Learn how to create and use custom AsyncStream sequences, continuations, task groups and custom actors. By Audrey Tam.
Learning path
This is part of the Concurrency in iOS learning path. View path.
Who is this for?
This course is for intermediate Swift developers who are familiar with writing asynchronous
applications with async/await, tasks and asynchronous sequences, and want to use custom AsyncStream
sequences, continuations, task groups and custom actors to write safer and more predictable asynchronous apps.
Covered concepts
- AsyncStream
- Manual continuations
- Unit testing & testing tools
- Task groups
- Custom actors
- Global actors
Part 1: AsyncStream & Continuations
Get an overview of what’s in this course and take a closer look at what you’ll learn in Part 1 — AsyncStream
, continuations, unit testing. Get introduced to the messaging app you’ll work on and review what you need to work through this course.
AsyncStream
is an easier way to create a custom AsyncSequence
. Convert the Typewriter AsyncSequence
from the previous course to unfolding (pull-based) and buffered (push-based) AsyncStreams.
Run the course server. Get an overview of Blabber’s chat()
function. Implement a countdown timer as an unfolding AsyncStream
.
Wrap the synchronous Notifications API in a buffered AsyncStream
to produce system messages when users leave the chat and return.
Challenge: Rewrite countdown(to:)
using the buffered push-based version of AsyncStream
and Timer.scheduledTimer
.
Add unit tests for the BlabberModel
type: To verify that BlabberModel
sends correct data to the server, you’ll configure a custom URLSession
for your tests to work with, write unit tests for say(_:)
and countdown(to:)
, and note timeout and duration issues that you’ll fix in Episode 9 Unit Testing Tools.
To implement the location button in Blabber, you’ll use a manual continuation to integrate the Core Location manager delegate pattern with Swift concurrency. You’ll inject a manual continuation into a custom Core Location manager delegate and use this to implement its delegate methods. This is a good way to handle a fixed number of delegate method calls.
The starter project contains a custom AddressCoder
to convert a location into the nearest address. To simulate a callback API, its method addressFor(location:)
requires a completion handler. You’ll wrap this in a manual continuation to post the user’s address as a Blabber message.
When you wrote unit tests in episode 6, there were two issues: await
doesn’t time out, and the tests take more than 5 seconds to run, because of the 1-second waits built into the countdown. In this episode, you’ll create unit testing tools to fix these issues: You’ll create a timeout mechanism, and you’ll speed up execution with a custom sleep
function.
Review what you learned in Part 1 and see what’s coming up in Part 2.
Part 2: Concurrent Code
Learn how to use TaskGroup
for truly dynamic concurrency and how to use the Actor
API to make your code thread-safe. Learn about the apps you’ll build in this part of the course and the Swift concurrency tools you’ll use.
Learn how to use TaskGroup
to create concurrency on the fly. The image download tasks in Sky are independent, so you can run them concurrently. Add them to a task group and set their priority to promote UI updates.
Learn how to safely process the results of concurrent tasks in a TaskGroup
and control the number of tasks running in parallel.
The EmojiArt app runs a lot of tasks in parallel: Concurrent tasks add downloaded images to a collection and update progress values, so there’s a lot of potential for data races. Learn how to use the Actor
protocol to protect shared mutable state and convert EmojiArtModel
from a class to an actor.
Examine the ImageLoader
actor and use it to provide an in-memory image cache for the EmojiArt app.
Learn how to create custom global actors, just like MainActor
, for situations where you need a single, shared actor that’s accessible from anywhere.
Create a custom GlobalActor
to provide a persistent, on-disk image cache that allows easy and safe access to shared resources from anywhere in your app.
Use your ImageDatabase
global actor to provide an on-disk image cache for the EmojiArt app. Implement the in-memory cache hit counter and clear button.
Challenge: Implement the on-disk cache hit counter and clear button.
Review what you learned in this course.