Modern Concurrency: Beyond the Basics

Oct 20 2022 · Swift 5.5, iOS 15, Xcode 13.4

Part 2: Concurrent Code

14. Actor

Episode complete

Play next episode

About this episode
Leave a rating/review
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 13. Using TaskGroup Next episode: 15. Writing Safe Concurrent Code With Actors

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Notes: 14. Actor

This episode displays the Xcode 14 documentation for Sendable — it’s more extensive than Xcode 13’s.

Heads up... You've reached locked video content where the transcript will be shown as obfuscated text.

For the rest of this course, you'll work with Emoji Art, an app that lets you browse an online catalog of digital emoji art. It reads the feed of current works of art from the server, verifies the digital signature of the images, and displays them on screen. This app runs a lot of tasks in parallel, and there's a lot of potential for data races when multiple concurrent tasks are adding downloaded images to a collection or updating progress values. Open the starter project. Then, build and run. For now, ignore this warning about updating the UI from background threads. You'll fix it shortly. One way to detect data races in your code is to enable the Thread Sanitizer in your Xcode project scheme. Click the scheme selector in Xcode's toolbar and select edit scheme. In the scheme editing window click run. Then, select the diagnostics tab. Check the Thread Sanitizer checkbox. Then, close this window. When you rebuild the project, Xcode adds some extra checks into your app. At runtime, these check whether your code concurrently mutates any data. Build and run. The app UI looks the same as before. Xcode, however, has a new purple runtime warning. It found a swift access race. Your warning message might mention a data race. If Thread Sanitizer detects a data race, your code will eventually crash in production. To protect verified count from concurrent access, you convert emoji art model from a class to an actor. Actors have a lot of typical class behavior, like by reference semantics, so the change won't be too complex. In the model group, open emoji art model dot swift. Replace the class keyword with actor. This changes the type of the model to an actor. Your shared state is now safe. Actors don't magically solve concurrent access. The compiler now follows the rules for actors and finds issues in code that used to compile. The compiler suggests how you should change your code to make it work safely in a concurrent context. But more importantly, when you use an actor, the compiler protects you against creating unsafe thread accesses in your code. Now, follow Xcode's suggestions to make the existing code thread safe. This error says actor-isolated property verified count cannot be mutated from a Sendable closure. You'll learn about Sendable soon. For now, just know you get the error because you can't update the actor state from outside its direct scope. All code that isn't confined to the serial executor of the actor is outside access. That means it includes calls from other types and concurrent tasks, like your task group in this case. Let's take a closer look at the actor protocol. The actor type is one of the concurrency-related improvements introduced in Swift 5.5. Actor is a programming type just like enum, struct, class, and so on, and it's a reference type, like class. An actor in Swift can safely access and mutate its own state. A special type called a serial executor, which the runtime manages, synchronizes all calls to the actor's members. The serial executor, like a serial dispatch queue in GCD, executes tasks one after another. By doing this, it protects the actor's state from concurrent access. Access to the actor from other types is automatically performed asynchronously, and is scheduled on the actor's serial executor. This is called the state isolation layer, which ensures that all state mutation is thread safe. Now, back to Xcode. To overcome the verified count issue, you'll extract the code to increment verified count into a method, then call it asynchronously. This allows the actor to serialize the calls to that method. Above verify images, add a new method. You can call this method synchronously from inside the actor's direct scope, but the compiler will enforce asynchronous access from outside of it. Now, replace self dot verified count += 1 in verify images. This new code makes calls to increase verified count serially, making sure you mutate your shared state safely, but there's still a lot of compiler errors. Now that image feed is part of your emoji art model actor, you can't access that property on the main actor. Swift UI runs on the main actor and can't read the feed anymore. You'll fix that next. You mostly use image feed to drive the app's UI, so it makes sense to place this property on the main actor. But how can you share it between the main actor and emoji art model? Well, you can use the at main actor attribute to move image feed to execute on the main actor while the property itself remains inside emoji art model. In emoji art model, locate image feed and assign it to the main actor. By moving image feed from the emoji art model serial executor to the main actor, image feed is now accessible from the main thread. Next is the error on the line that calls image feed dot for each. To access the actor, you need to call image feed for each asynchronously. Easy, just await it. To fix the errors in load images, wrap image feed dot remove all in your old friend await main actor dot run. And do the same for image feed equals list. You run the two calls asynchronously on the main actor where it's safe to update the UI. Just one more error to fix, quick open loading view. The final error is on the line right at the end that calculates the value of progress. Wrap it in a task and await it. Congratulations, you followed Xcode's guidance to fix all the unsafe code. Build and run again. This time, no purple warnings. You've worked through designing your first actor type and experienced some actor-specific compiler behavior, but there's one topic you skipped over. What is the Sendable type that those compiler errors mentioned? Coming up next. Sendable is a protocol that indicates that a given value is safe to use in concurrent code. Open Window Developer documentation. And look up Sendable. I'm using Xcode 14. It's Sendable documentation is more extensive than Xcode 13's. Scroll down to the Inherited By section. The actor protocol is Sendable, so actor instances are safe to use in concurrent code. No surprise. Scroll down to the Conforming Type section. Many types are Sendable by default. Async types, value types like array, dictionary, bool, double, int, and others are all safe to use in concurrent code. Value types are safe because value semantics prevent you from accidentally mutating a shared reference to the same object. Classes are generally not Sendable because they're by reference semantics allow you to mutate the same instance in memory. The Sendable protocol has no requirements. You really only use it to annotate types that you know are safe to use across threads. Once you add Sendable conformance to one of your types, the compiler will automatically limit it in various ways to help you ensure its thread safety. For example, it'll ask you to make class final or make class properties immutable. Let's take a closer look at how this works in the task type. You use the @Sendable attribute to require thread safe values in your code. For example, the task type creates an asynchronous task that could unsafely mutate shared safe, so the task in the declaration requires that the operation closure is Sendable. Add task also requires a Sendable closure, therefore, the best practice in your own code is to require that any closures you run asynchronously are at Sendable and that any values you use in asynchronous code adhere to the Sendable protocol. And if your structure or class is thread safe, you should also add Sendable conformance so other concurrent code can use it safely. Now, that you've moved image feed off your custom actor and onto main actor, the methods that work with the feed don't actually work with your actor's shared state directly. In Emoji Art model, load images and download image don't have any state to protect anymore. Therefore, they don't need the actor behavior at all. When methods like that are safe, you can mark them with the non-isolated keyword. This speeds up the runtime by removing the safety harness around them. Mark load images as non-isolated. And also download image. Now Xcode treats these two methods like normal class methods instead of actor methods. In the next episode, you'll use a more complex actor, an image cache.