Previous episode: 02. Introduction to Modern Concurrency
Next episode: 04. Challenge: Run Code on the Main Thread
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.
Continuing from the previous episode, let's take a look at more of Swift's modern concurrency features. Begin by opening this episode's starter playground. This playground contains the same fetchDomains function you wrote in the previous episode, as well as new model code for playlists. We'll take a look at that new code later in this episode. For now, another cool capability of Swift's concurrency is asynchronous sequences. These work similar to sequence, where you can get a list of values one step at a time except asynchronously. You await to receive values in the sequence as they become available. Add the following code at the bottom of your playground. Url has a property called lines that returns an asynchronous sequence of strings for each file line. You loop over the strings with for try await, which can return the title as soon as it finds a line with the correct tag in it. To test this, add the following code at the bottom of your playground. This code should look familiar given the concepts you learned about in the previous episode. Go ahead and run your code. Take a look at the printed results, just what you wanted. Read-only computed properties can also be asynchronous, or async. Add the following code at the bottom of your playground. This adds a static async property on your existing Domains type. Because fetchDomains is asynchronous and can throw an error, you mark the property's getter as async throws. Try your new property by adding the following code. Run your playground. Pretty neat, huh? Read-only subscripts can also be asynchronous. Add this code to your playground. Very similar to the async read-only property except this allows you to asynchronously subscript into your type to get the element you want. Run your playground. The expected result gets printed via your async subscript. Yay. You've seen how to add concurrency to your code, functions and properties. Concurrency has risks, however, especially when multiple code paths are trying to access and change the same state at once. Think of a bank's ATM. If two people try to withdraw the entire account balance at the same exact time and the code that powers this isn't written carefully, both withdrawals will succeed and the bank will end up giving out more money than what was in the account. While this is difficult to actually happen and banks have mechanisms for this in place, it helps us understand the issue at hand. Swift concurrency includes the actor type that can help with this type of problem. In your playground, open the Playlist.swift file and take a look at the Playlist class. There are four methods in the class that can all mutate songs at the same time. These methods are not thread safe, a term often used to indicate they are not safe to use concurrently. Think of the bank ATM example. Your intuition and existing knowledge may tell you to just mark them as async to make them concurrent, but then you'd have multiple tasks changing the state of a playlist simultaneously. To solve this problem, you can convert Playlist from a class to an actor. Just like classes, actors are reference types that represent a shared mutable state. Actors, however, prevent concurrent access to their state, so only one method can access and modify the state of a playlist at any time. To convert Playlist from a class to an actor, start by changing the class declaration to the following. Next, make both move methods async. Finally, because you access two playlists, self and the playlist passed in as a parameter, you need to await the calls to add or remove songs on Playlist so no one else tries to access it while this method is executing. Add the following await calls inside of each of the move methods. With that, you can now safely use playlists in concurrent code. Test out your new code by removing all existing code in your playground and adding the following at the bottom. You use await to isolate the actor, and that also indicates that the new method can suspend if some other piece of code is in the middle of accessing the playlist. Because only one piece of code can access Playlist at any given time, this code is now thread safe or concurrency safe. Note how inside the move methods you don't use await when calling add or remove. This is because the compiler already knows you have exclusive access to the instance. Run your code and look at the results. Everything works well and without errors, even if things are taking place asynchronously. A fun piece of info is that your playlist actor actually prepares two internal methods for every method of the actor, one version that is concurrent and needs await and another faster version that doesn't. Depending on how you call your method, the compiler knows which version to use to optimize performance. So far, you've seen how to make your code concurrent and run in background threads, as opposed to on the main thread. What happens when you want to switch back to the main thread in order to perhaps perform some UI updates? This is where MainActor comes into play. We briefly discussed MainActor in the previous episode, but as a recap, MainActor is a global actor that provides an executor to perform tasks on the main thread. To try out an example scenario, remove all code from your playground and add the following. This code uses the same concept you've learned about so far. You create a URL and then download its data using URLSession's closure-based dataTask method. Inside the completion closure for the dataTask, you verify the response and ensure you can decode it from JSON to an array of domains. In case of failure, you print an error and return. Otherwise, you print your domains and a Boolean indicating whether your code is executing on the main thread or MainActor. Notice, however, the new syntax within Task. In order to run a piece of code on the MainActor or main thread from anywhere, you can use MainActor's run, which takes a closure with the work to perform. Because run is asynchronous, you mark it as await and run it inside of a Task. Run your code and look at the results. If you're coming from using dispatch queues in Swift in order to execute work on the main thread, this is the equivalent of Swift's modern concurrency features and syntax. Personally, I find it more readable and easy to follow than the closure-heavy, oftentimes non-intuitive syntax of dispatch queues. Alternatively, instead of calling run on MainActor, you can mark the Task closure to run on the MainActor. Replace the Task code with the following. Run your code. What if you want a function or a method on your types to run on the MainActor? To see an example of how to do this, add the following code to your playground. This extension on Domains adds a method that returns all the names of your domains as a string array. You download the domains using URLSession, as you've seen before, and print the domains if you're able to successfully parse the response. Run your playground and take a look at the console. Your domainNames method is not running on the main thread, even though you know we always want it to. While you could use the same pattern you just learned about when wanting to run a task on MainActor, it's easy to forget to wrap the call to domainNames in order to always run it on the main thread. Wouldn't it be easier if there was a way to tell Swift that this method should always run on the main thread? There is. Update your method declaration with the following. The @MainActor attribute tells Swift that you always want to run this method on the MainActor or main thread. Your playground may be giving you an error now about making a call to an actor-isolated method from a nonisolated context. Fix that by updating the last print statement with the following. Now run your code and look at the results. As expected, domainNames gets executed on the main thread. Woohoo! You've covered all of the concurrency topics you needed in order to have an easier time working with URLSession. Before getting started with our deep dive into URLSession, however, it's time for a small challenge to help put in practice the concepts you just learned. See ya there.
All videos. All books.
One low price.
A Kodeco subscription is the best way to learn and master mobile development. Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.