---
title: Protect mutable state with Swift actors - WWDC21
source: https://developer.apple.com/videos/play/wwdc2021/10133/
timestamp: 2026-05-28T11:33:23.386Z
---

# Protect mutable state with Swift actors - WWDC21

**Collection:** wwdc2021

**Video:** 10133

## Transcript

- [00:02] ♪ Bass music playing ♪
- [00:07] ♪
- [00:09] Dario Rexin: Hi, my name is Dario Rexin,
- [00:11] and I am an engineer on the Swift team here at Apple.
- [00:14] Today, my colleague Doug and I will talk about actors in Swift
- [00:17] and how they are utilized to protect mutable state
- [00:20] in concurrent Swift applications.
- [00:22] One of the fundamentally hard problems
- [00:24] with writing concurrent programs is avoiding data races.
- [00:29] Data races occur when two separate threads
- [00:31] concurrently access the same data
- [00:33] and at least one of those accesses is a write.
- [00:37] Data races are trivial to construct
- [00:39] but are notoriously hard to debug.
- [00:42] Here's a simple counter class with one operation
- [00:44] that increments the counter and returns its new value.
- [00:48] Let's say we go ahead and try to increment
- [00:50] from two concurrent tasks.
- [00:52] This is a bad idea.
- [00:54] Depending on the timing of the execution,
- [00:56] we might get 1 and then 2, or 2 and then 1.
- [01:00] This is expected, and in both cases,
- [01:02] the counter would be left in a consistent state.
- [01:05] But because we've introduced a data race,
- [01:07] we could also get 1 and 1 if both tasks read a 0
- [01:11] and write a 1.
- [01:12] Or even 2 and 2 if the return statements happen
- [01:15] after both increment operations.
- [01:18] Data races are notoriously hard to avoid and debug.
- [01:22] They require nonlocal reasoning because the data accesses
- [01:25] causing the race might be in different parts of the program.
- [01:29] And they are nondeterministic because the operating system's
- [01:32] scheduler might interleave the concurrent tasks
- [01:35] in different ways each time you run your program.
- [01:38] Data races are caused by shared mutable state.
- [01:42] If your data doesn't change
- [01:43] or it isn't shared across multiple concurrent tasks,
- [01:46] you can't have a data race on it.
- [01:49] One way to avoid data races
- [01:51] is to eliminate shared mutable state
- [01:53] by using value semantics.
- [01:56] With a variable of a value type, all mutation is local.
- [02:00] Moreover, "let" properties of value-semantic types
- [02:03] are truly immutable,
- [02:05] so it's safe to access them from different concurrent tasks.
- [02:09] Swift has been promoting value semantics since its inception
- [02:12] because they make it easier to reason about our program
- [02:15] and those same things also make them safe
- [02:17] to use in concurrent programs.
- [02:20] In this example, we create an array with some values.
- [02:24] Next, we assign that array to a second variable.
- [02:28] Now we append a different value to each copy of the array.
- [02:32] When we print both arrays at the end,
- [02:34] we see that both copies contain the values
- [02:37] that the array was initialized with,
- [02:39] but each appended value is only present
- [02:41] in the respective copy we appended them to.
- [02:44] The majority of types in Swift's standard library
- [02:47] have value semantics, including collection types
- [02:50] like dictionary, or as in this example, array.
- [02:55] Now that we have established that value semantics
- [02:57] solve all of our data races,
- [02:59] let's go ahead and make our counter a value type
- [03:02] by turning it into a struct.
- [03:05] We also have to mark the increment function as mutating,
- [03:08] so it can modify the value property.
- [03:12] When we are now trying to modify the counter
- [03:14] we will get a compiler error because the counter is a let,
- [03:17] which prevents us from mutating it.
- [03:21] Now, it seems very tempting to just change the counter variable
- [03:24] to a var to make it mutable.
- [03:27] But that would leave us, again, with a race condition
- [03:30] because the counter would be referenced
- [03:31] by both concurrent tasks.
- [03:34] Luckily, the compiler has us covered
- [03:36] and does not allow us to compile this unsafe code.
- [03:41] We can instead assign the counter
- [03:42] to a local mutable variable inside each concurrent task.
- [03:47] When we execute our example now,
- [03:50] it will always print 1 for both concurrent tasks.
- [03:54] But even though our code is now race-free,
- [03:56] the behavior is not what we want anymore.
- [03:59] This goes to show that there are still cases
- [04:01] where shared mutable state is required.
- [04:06] When we have shared mutable state in a concurrent program,
- [04:09] we need some form of synchronization
- [04:12] to ensure that concurrent use of our shared mutable state
- [04:15] won't cause data races.
- [04:17] There are a number of primitives for synchronization,
- [04:19] from low-level tools like atomics and locks
- [04:22] to higher-level constructs like serial dispatch queues.
- [04:27] Each of these primitives has its various strengths,
- [04:30] but they all share the same critical weakness:
- [04:33] they require careful discipline to use exactly correctly,
- [04:37] every single time,
- [04:38] or we'll end up with a data race.
- [04:40] This is where actors come in.
- [04:43] Actors are a synchronization mechanism
- [04:44] for shared mutable state.
- [04:47] An actor has its own state
- [04:49] and that state is isolated from the rest of the program.
- [04:53] The only way to access that state
- [04:55] is by going through the actor.
- [04:57] And whenever you go through the actor,
- [04:59] the actor's synchronization mechanism ensures
- [05:01] that no other code is concurrently
- [05:03] accessing the actor's state.
- [05:05] This gives us the same mutual exclusion property
- [05:08] that we get from manually using locks
- [05:10] or serial dispatch queues, but with actors,
- [05:12] it is a fundamental guarantee provided by Swift.
- [05:16] You can't forget to perform the synchronization,
- [05:18] because Swift will produce a compiler error if you try.
- [05:22] Actors are a new kind of type in Swift.
- [05:24] They provide the same capabilities
- [05:26] as all of the named types in Swift.
- [05:29] They can have properties, methods, initializers,
- [05:32] subscripts, and so on.
- [05:34] They can conform to protocols
- [05:36] and be augmented with extensions.
- [05:39] Like classes, they are reference types;
- [05:41] because the purpose of actors is to express shared mutable state.
- [05:45] In fact, the primary distinguishing characteristic
- [05:48] of actor types is that they isolate their instance data
- [05:51] from the rest of the program
- [05:53] and ensure synchronized access to that data.
- [05:56] All of their special behavior follows from those core ideas.
- [06:00] Here, we've defined our counter as an actor type.
- [06:03] We still have the instance property value
- [06:05] for the counter,
- [06:07] and the increment method to increment that value
- [06:09] and return the new value.
- [06:11] The difference is that the actor will ensure the value
- [06:14] isn't accessed concurrently.
- [06:16] In this case, that means the increment method,
- [06:18] when called, will run to completion
- [06:20] without any other code executing on the actor.
- [06:23] That guarantee eliminates the potential for data races
- [06:27] on the actor's state.
- [06:29] Let's bring back our data race example.
- [06:33] We again have two concurrent tasks
- [06:35] attempting to increment the same counter.
- [06:37] The actor's internal synchronization mechanism
- [06:40] ensures that one increment call executes to completion
- [06:43] before the other can start.
- [06:46] So we can get 1 and 2 or 2 and 1
- [06:49] because both are valid concurrent executions,
- [06:52] but we cannot get the same count twice
- [06:55] or skip any values because the internal synchronization
- [06:58] of the actor has eliminated the potential for data races
- [07:01] on the actor state.
- [07:04] Let's consider what actually happens
- [07:05] when both concurrent tasks try to increment the counter
- [07:08] at the same time.
- [07:11] One will get there first,
- [07:12] and the other will have to wait its turn.
- [07:14] But how can we ensure that the second task
- [07:17] can patiently await its turn on the actor?
- [07:20] Swift has a mechanism for that.
- [07:22] Whenever you interact with an actor from the outside,
- [07:24] you do so asynchronously.
- [07:27] If the actor is busy, then your code will suspend
- [07:29] so that the CPU you're running on can do other useful work.
- [07:34] When the actor becomes free again,
- [07:35] it will wake up your code -- resuming execution --
- [07:38] so the call can run on the actor.
- [07:41] The await keyword in this example indicates
- [07:44] that the asynchronous call to the actor
- [07:46] might involve such a suspension.
- [07:50] Let's stretch our counterexample just a bit further
- [07:52] by adding an unnecessarily slow reset operation.
- [07:56] This operation sets the value back to 0,
- [07:59] then calls increment an appropriate number of times
- [08:02] to get the counter to the new value.
- [08:05] This resetSlowly method is defined in an extension
- [08:08] of the counter actor type so it is inside the actor.
- [08:12] That means it can directly access the actor's state,
- [08:15] which it does to reset the counter value to 0.
- [08:19] It can also synchronously call other methods on the actor,
- [08:23] such as in the call to increment.
- [08:26] There's no await required because we already know
- [08:29] we're running on the actor.
- [08:31] This is an important property of actors.
- [08:34] Synchronous code on the actor always runs to completion
- [08:36] without being interrupted.
- [08:39] So we can reason about synchronous code sequentially,
- [08:42] without needing to consider the effects of concurrency
- [08:44] on our actor state.
- [08:47] We have stressed that our synchronous code
- [08:49] runs uninterrupted, but actors often interact with each other
- [08:53] or with other asynchronous code in the system.
- [08:56] Let's take a few minutes to talk about asynchronous code
- [08:58] and actors.
- [09:00] But first, we need a better example.
- [09:02] Here we are building an image downloader actor.
- [09:05] It is responsible for downloading an image
- [09:07] from another service.
- [09:09] It also stores downloaded images in a cache
- [09:12] to avoid downloading the same image multiple times.
- [09:16] The logical flow is straightforward:
- [09:18] check the cache, download the image,
- [09:21] then record the image in the cache before returning.
- [09:26] Because we are in an actor,
- [09:27] this code is free from low-level data races;
- [09:30] any number of images can be downloaded concurrently.
- [09:33] The actor's synchronization mechanisms guarantee
- [09:36] that only one task can execute code
- [09:38] that accesses the cache instance property at a time,
- [09:41] so there is no way that the cache can be corrupted.
- [09:45] That said, the await keyword here
- [09:47] is communicating something very important.
- [09:51] Whenever an await occurs,
- [09:53] it means that the function can be suspended at this point.
- [09:57] It gives up its CPU so other code in the program can execute,
- [10:00] which affects the overall program state.
- [10:04] At the point where your function resumes,
- [10:06] the overall program state will have changed.
- [10:09] It is important to ensure that you haven't made assumptions
- [10:12] about that state prior to the await
- [10:14] that may not hold after the await.
- [10:18] Imagine we have two different concurrent tasks
- [10:20] trying to fetch the same image at the same time.
- [10:23] The first sees that there is no cache entry,
- [10:26] proceeds to start downloading the image from the server,
- [10:29] and then gets suspended because the download will take a while.
- [10:33] While the first task is downloading the image,
- [10:35] a new image might be deployed to the server under the same URL.
- [10:40] Now, a second concurrent task tries to fetch the image
- [10:42] under that URL.
- [10:44] It also sees no cache entry
- [10:46] because the first download has not finished yet,
- [10:49] then starts a second download of the image.
- [10:52] It also gets suspended while its download completes.
- [10:56] After a while, one of the downloads --
- [10:58] let's assume it's the first -- will complete
- [11:01] and its task will resume execution on the actor.
- [11:04] It populates the cache
- [11:06] and returns the resulting image of a cat.
- [11:09] Now the second task has its download complete,
- [11:12] so it wakes up.
- [11:13] It overwrites the same entry in the cache
- [11:15] with the image of the sad cat that it got.
- [11:18] So even though the cache was already populated with an image,
- [11:22] we now get a different image for the same URL.
- [11:25] That's a bit of a surprise.
- [11:27] We expected that once we cache an image,
- [11:29] we always get that same image back for the same URL
- [11:32] so our user interface remains consistent,
- [11:35] at least until we go and manually clear out of the cache.
- [11:38] But here, the cached image changed unexpectedly.
- [11:42] We don't have any low-level data races,
- [11:44] but because we carried assumptions about state
- [11:46] across an await,
- [11:47] we ended up with a potential bug.
- [11:49] The fix here is to check our assumptions after the await.
- [11:53] If there's already an entry in the cache when we resume,
- [11:56] we keep that original version and throw away the new one.
- [12:00] A better solution would be
- [12:01] to avoid redundant downloads entirely.
- [12:04] We've put that solution in the code
- [12:05] associated with this video.
- [12:08] Actor reentrancy prevents deadlocks
- [12:10] and guarantees forward progress,
- [12:12] but it requires you to check your assumptions
- [12:14] across each await.
- [12:16] To design well for reentrancy,
- [12:18] perform mutation of actor state within synchronous code.
- [12:22] Ideally, do it within a synchronous function
- [12:25] so all state changes are well-encapsulated.
- [12:29] State changes can involve temporarily putting our actor
- [12:32] into an inconsistent state.
- [12:34] Make sure to restore consistency before an await.
- [12:38] And remember that await is a potential suspension point.
- [12:41] If your code gets suspended,
- [12:43] the program and world will move on
- [12:45] before your code gets resumed.
- [12:47] Any assumptions you've made about global state,
- [12:50] clocks, timers, or your actor will need to be checked
- [12:53] after the await.
- [12:55] And now my colleague Doug will tell you more
- [12:57] about actor isolation. Doug?
- [13:01] Doug Gregor: Thanks, Dario.
- [13:03] Actor isolation is fundamental to the behavior of actor types.
- [13:08] Dario discussed how actor isolation is guaranteed
- [13:12] by the Swift language model,
- [13:13] through asynchronous interactions
- [13:15] from outside the actor.
- [13:17] In this section, we'll talk about how actor isolation
- [13:20] interacts with other language features,
- [13:22] including protocol conformances, closures, and classes.
- [13:29] Like other types, actors can conform to protocols
- [13:32] so long as they can satisfy the requirements of the protocol.
- [13:36] For example, let's make this LibraryAccount actor
- [13:39] conform to the Equatable protocol.
- [13:42] The static equality method compares two library accounts
- [13:46] based on their ID numbers.
- [13:48] Because the method is static, there is no self instance
- [13:51] and so it is not isolated to the actor.
- [13:55] Instead, we have two parameters of actor type,
- [13:58] and this static method is outside of both of them.
- [14:02] That's OK because the implementation is only accessing
- [14:05] immutable state on the actor.
- [14:09] Let's extend our example further
- [14:11] to make our library account conform
- [14:13] to the Hashable protocol.
- [14:15] Doing so requires implementing the hash(into) operation,
- [14:18] which we can do like this.
- [14:20] However, the Swift compiler will complain
- [14:23] that this conformance isn't allowed.
- [14:26] What happened?
- [14:28] Well, conforming to Hashable this way means that
- [14:31] this function could be called from outside the actor,
- [14:34] but hash(into) is not async,
- [14:37] so there is no way to maintain actor isolation.
- [14:41] To fix this, we can make this method nonisolated.
- [14:46] Nonisolated means that this method is treated
- [14:49] as being outside the actor,
- [14:51] even though it is, syntactically,
- [14:53] described on the actor.
- [14:55] This means that it can satisfy the synchronous requirement
- [14:59] from the Hashable protocol.
- [15:02] Because nonisolated methods
- [15:03] are treated as being outside the actor,
- [15:06] they cannot reference mutable state on the actor.
- [15:09] This method is fine
- [15:10] because it's referring to the immutable ID number.
- [15:14] If we were to try to hash based on something else,
- [15:18] such as the array of books on loan, we will get an error
- [15:22] because access to mutable state from the outside
- [15:25] would permit data races.
- [15:28] That's enough of protocol conformances.
- [15:30] Let's talk about closures.
- [15:33] Closures are little functions
- [15:35] that are defined within one function,
- [15:37] that can then be passed to another function
- [15:39] to be called some time later.
- [15:42] Like functions, a closure might be actor-isolated
- [15:45] or it might be nonisolated.
- [15:48] In this example, we're going to read some
- [15:51] from each book we have on loan
- [15:52] and return the total number of pages we've read.
- [15:55] The call to reduce involves a closure
- [15:58] that performs the reading.
- [16:00] Note that there is no await in this call to readSome.
- [16:03] That's because this closure,
- [16:05] which is formed within the actor-isolated function "read",
- [16:08] is itself actor-isolated.
- [16:11] We know this is safe because the reduce operation
- [16:14] is going to execute synchronously,
- [16:16] and can't escape the closure out to some other thread
- [16:19] where it could cause concurrent access.
- [16:22] Now, let's do something a little different.
- [16:25] I don't have time to read just now,
- [16:27] so let's read later.
- [16:30] Here, we create a detached task.
- [16:32] A detached task executes the closure concurrently
- [16:36] with other work that the actor is doing.
- [16:38] Therefore, the closure cannot be on the actor
- [16:41] or we would introduce data races.
- [16:44] So this closure is not isolated to the actor.
- [16:47] When it wants to call the read method,
- [16:49] it must do so asynchronously, as indicated by the await.
- [16:55] We've talked a bit about actor isolation of code,
- [16:58] which is whether that code runs inside the actor or outside it.
- [17:02] Now, let's talk about actor isolation and data.
- [17:07] In our library account example,
- [17:09] we've studiously avoided saying what the book type actually is.
- [17:14] I've been assuming it's a value type,
- [17:16] like a struct.
- [17:17] That's a good choice because it means that
- [17:19] all the state for an instance of the library account actor
- [17:22] is self-contained.
- [17:25] If we go ahead and call this method
- [17:26] to select a random book to read,
- [17:29] we'll get a copy of the book that we can read.
- [17:32] Changes we make to our copy of the book
- [17:34] won't affect the actor and vice versa.
- [17:39] However, if the turn the book into a class,
- [17:42] things are a little different.
- [17:45] Our library account actor now references instances
- [17:48] of the book class.
- [17:49] That's not a problem in itself.
- [17:52] However, what happens when we call the method
- [17:55] to select a random book?
- [17:58] Now we have a reference into the mutable state of the actor,
- [18:02] which has been shared outside of the actor.
- [18:05] We've created the potential for data races.
- [18:09] Now, if we go and update the title of the book,
- [18:12] the modification happens in state that is accessible
- [18:15] within the actor.
- [18:18] Because the visit method is not on the actor,
- [18:20] this modification could end up being a data race.
- [18:25] Value types and actors are both safe to use concurrently,
- [18:29] but classes can still pose problems.
- [18:32] We have a name for types that are safe to use concurrently:
- [18:35] Sendable.
- [18:38] A Sendable type is one whose values can be shared
- [18:42] across different actors.
- [18:44] If you copy a value from one place to another,
- [18:47] and both places can safely modify their own copies
- [18:50] of that value without interfering with each other,
- [18:53] the type can be Sendable.
- [18:56] Value types are Sendable because each copy is independent,
- [18:59] as Dario talked about earlier.
- [19:02] Actor types are Sendable
- [19:04] because they synchronize access to their mutable state.
- [19:08] Classes can be Sendable,
- [19:10] but only if they are carefully implemented.
- [19:12] For example, if a class and all of its subclasses
- [19:15] only hold immutable data,
- [19:17] then it can be called Sendable.
- [19:20] Or if the class internally performs synchronization,
- [19:23] for example with a lock,
- [19:25] to ensure safe concurrent access, it can be Sendable.
- [19:29] But most classes are neither of these,
- [19:31] and cannot be Sendable.
- [19:34] Functions aren't necessarily Sendable,
- [19:37] so there is a new kind of function type
- [19:39] for functions that are safe to pass across actors.
- [19:42] We'll get back to those shortly.
- [19:44] Your actors --
- [19:46] in fact, all of your concurrent code --
- [19:48] should primarily communicate in terms of Sendable types.
- [19:52] Sendable types protect code from data races.
- [19:55] This is a property
- [19:56] that Swift will eventually start checking statically.
- [19:59] At that point,
- [20:00] it will become an error to pass a non-Sendable type
- [20:03] across actor boundaries.
- [20:06] How does one know that a type is Sendable?
- [20:09] Well, Sendable is a protocol,
- [20:11] and you state that your type conforms to Sendable
- [20:13] the same way you do with other protocols.
- [20:16] Swift will then check to make sure your type
- [20:19] makes sense as a Sendable type.
- [20:22] A Book struct can be Sendable if all of its stored properties
- [20:25] are of Sendable type.
- [20:27] Let's say Author is actually a class,
- [20:30] which means it -- and therefore the array of authors --
- [20:34] are not Sendable.
- [20:36] Swift will produce a compiler error
- [20:38] indicating that Book cannot be Sendable.
- [20:43] For generic types, whether they are Sendable
- [20:45] can depend on their generic arguments.
- [20:48] We can use conditional conformance
- [20:50] to propagate Sendable when it's appropriate.
- [20:53] For example, a pair type will be Sendable
- [20:56] only when both of its generic arguments are Sendable.
- [21:00] The same approach is used to conclude that an array
- [21:03] of Sendable types is itself Sendable.
- [21:07] We encourage you introduce Sendable conformances
- [21:10] to the types whose values are safe to share concurrently.
- [21:14] Use those types within your actors.
- [21:16] Then when Swift begins to start enforcing Sendable
- [21:19] across actors, your code will be ready.
- [21:23] Functions themselves can be Sendable,
- [21:25] meaning that it is safe to pass the function value
- [21:28] across actors.
- [21:29] This is particularly important for closures where it restricts
- [21:33] what the closure can do to help prevent data races.
- [21:37] For example, a Sendable closure
- [21:39] cannot capture a mutable local variable,
- [21:42] because that would allow data races
- [21:43] on the local variable.
- [21:46] Anything the closure does capture
- [21:48] needs to be Sendable,
- [21:49] to make sure that the closure cannot be used
- [21:52] to move non-Sendable types across actor boundaries.
- [21:55] And finally, a synchronous Sendable closure
- [21:58] cannot be actor-isolated, because that would allow code
- [22:01] to be run on the actor from the outside.
- [22:05] We've actually be relying on the idea of Sendable closures
- [22:08] in this talk.
- [22:10] The operation that creates detached tasks
- [22:13] takes a Sendable function,
- [22:14] written here with the @Sendable in the function type.
- [22:20] Remember our counterexample from the beginning of the talk?
- [22:23] We were trying to build a value-typed counter.
- [22:26] Then, we tried to go and modify it
- [22:28] from two different closures at the same time.
- [22:33] This would be a data race on the mutable local variable.
- [22:36] However, because the closure for a detached task is Sendable,
- [22:40] Swift will produce an error here.
- [22:44] Sendable function types are used
- [22:46] to indicate where concurrent execution can occur,
- [22:49] and therefore prevent data races.
- [22:51] Here's another example we saw earlier.
- [22:55] Because the closure for the detached task is Sendable,
- [22:58] we know that it should not be isolated to the actor.
- [23:02] Therefore, interactions with it will have to be asynchronous.
- [23:08] Sendable types and closures help maintain actor isolation
- [23:12] by checking that mutable state isn't shared across actors,
- [23:16] and cannot be modified concurrently.
- [23:20] We've been talking primarily about actor types,
- [23:23] and how they interact with protocols, closures,
- [23:26] and Sendable types.
- [23:28] There is one more actor to discuss --
- [23:30] a special one that we call the main actor.
- [23:34] When you are building an app,
- [23:36] you need to think about the main thread.
- [23:38] It is where the core user interface rendering happens,
- [23:42] as well as where user interaction events
- [23:44] are processed.
- [23:46] Operations that work with the UI
- [23:48] generally need to be performed from the main thread.
- [23:51] However, you don't want to do all of your work
- [23:54] on the main thread.
- [23:55] If you do too much work on the main thread, say,
- [23:58] because you have some slow input/output operation
- [24:01] or blocking interaction with a server,
- [24:03] your UI will freeze.
- [24:06] So, you need to be careful to do work on the main thread
- [24:09] when it interacts with the UI
- [24:11] but get off the main thread quickly
- [24:13] for computationally expensive or long-waiting operations.
- [24:18] So, we do work off the main thread when we can
- [24:21] and then call DispatchQueue.main.async
- [24:23] in your code whenever you have a particular operation
- [24:26] that must be executed on the main thread.
- [24:29] Stepping back from the details of the mechanism,
- [24:32] the structure of this code looks vaguely familiar.
- [24:35] In fact, interacting with the main thread
- [24:38] is a whole lot like interacting with an actor.
- [24:41] If you know you're already running on the main thread,
- [24:43] you can safely access and update your UI state.
- [24:47] If you aren't running on the main thread,
- [24:49] you need to interact with it asynchronously.
- [24:52] This is exactly how actors work.
- [24:55] There's a special actor to describe the main thread,
- [24:58] which we call the main actor.
- [25:02] The main actor is an actor
- [25:03] that represents the main thread.
- [25:06] It differs from a normal actor in two important ways.
- [25:10] First, the main actor performs all of its synchronization
- [25:14] through the main dispatch queue.
- [25:16] This means that, from a runtime perspective,
- [25:19] the main actor is interchangeable
- [25:21] with using DispatchQueue.main.
- [25:24] Second, the code and data that needs to be on the main thread
- [25:28] is scattered everywhere.
- [25:30] It's in SwiftUI, AppKit, UIKit, and other system frameworks.
- [25:35] It's spread across your own views, view controllers,
- [25:38] and the UI-facing parts of your data model.
- [25:41] With Swift concurrency, you can mark a declaration
- [25:44] with the main actor attribute to say that it must be executed
- [25:47] on the main actor.
- [25:49] We've done that with the checked-out operation here,
- [25:51] so it always runs on the main actor.
- [25:54] If you call it from outside the main actor,
- [25:57] you need to await, so that the call will be performed
- [26:00] asynchronously on the main thread.
- [26:04] By marking code that must run on the main thread
- [26:06] as being on the main actor, there is no more guesswork
- [26:09] about when to use DispatchQueue.main.
- [26:12] Swift ensures that this code
- [26:14] is always executed on the main thread.
- [26:20] Types can be placed on the main actor as well,
- [26:22] which makes all of their members and subclasses
- [26:25] be on the main actor.
- [26:26] This is useful for the parts of your code base
- [26:29] that must interact with the UI,
- [26:31] where most everything needs to run on the main thread.
- [26:34] Individual methods can opt-out via the nonisolated keyword,
- [26:38] with the same rules you're familiar with
- [26:40] from normal actors.
- [26:43] By using the main actor for your UI-facing types
- [26:46] and operations, and introducing your own actors
- [26:49] for managing other program state,
- [26:51] you can architect your app to ensure safe,
- [26:54] correct use of concurrency.
- [26:57] In this session, we've talked about how actors
- [27:00] protect their mutable state from concurrent access,
- [27:03] using actor isolation and by requiring asynchronous access
- [27:07] from outside the actor to serialize execution.
- [27:11] Use actors to build safe, concurrent abstractions
- [27:14] in your Swift code.
- [27:16] In implementing your actors, and in any asynchronous code,
- [27:20] always design for reentrancy; an await in your code
- [27:24] means the world can move on and invalidate your assumptions.
- [27:29] Value types and actors work together
- [27:31] to eliminate data races.
- [27:33] Be aware of classes that don't handle
- [27:36] their own synchronization, and other non-Sendable types
- [27:39] that reintroduce shared mutable state.
- [27:42] Finally, use the main actor on your code
- [27:45] that interacts with the UI to ensure that the code
- [27:48] that must be on the main thread always runs on the main thread.
- [27:53] To learn more about how to use actors
- [27:55] within your own application, check out our session
- [27:58] on updating an app for Swift concurrency.
- [28:01] And to learn more about the implementation
- [28:03] of Swift's concurrency model, including actors,
- [28:06] check out our "Behind the scenes" session.
- [28:11] Actors are a core part of the Swift concurrency model.
- [28:14] They work together with async/await
- [28:17] and structured concurrency to make it easier to build
- [28:20] correct and efficient concurrent programs.
- [28:23] We can't wait to see what you build with them.
- [28:25] ♪

---

*Extracted by [sosumi.ai](https://sosumi.ai) - Making Apple docs AI-readable.*
*This is unofficial content. All transcripts belong to Apple Inc.*
