Modern Concurrency: Getting Started

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

Part 1: Asynchronous Code

02. Getting Started

Episode complete

Play next episode

About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 01. Introduction Next episode: 03. Your First Asynchronous App
Transcript: 02. Getting Started In the course materials locate the starter playground and open it. Notice you import SwiftUI. This pulls in the private _concurrency framework that defines task. Import UIKit also works. The leading _ of _concurrency indicates the name could change in a future release. So importing SwiftUI or UIKit future proofs your code better than directly importing _concurrency. Here's some ordinary sequential code. Main actor is a type that runs code on the main thread. It's the modern alternative to DispatchQueue.main. As they're written now, all four lines of code run on the main actor, the main thread. Run this code by clicking the run button next to the last line. This code runs sequentially so the print messages appear in the same order as they're written. Now wrap the first three lines in a task. The task type takes a trailing closure where you write the work you want to do and run this code. Now the main actor message appears before the sum. If your sum still appears before the main actor statement, just add more zero so it takes longer to run. The code and the task closure runs sequentially, but the task itself runs on a background thread. While it's computing sum, the main thread runs the next line of code so the main actor message appears before the task prints sum. Comment out all this code then scroll down to see some code that helps you check if code is running on the main actor. This code sets a specific key value for the main actor then checks for this value in the playground and in the task. Run this code. And here's your confirmation. So the task runs on a background thread. What if you have more than one task? Scroll up and uncomment the earlier task code. Then duplicate the task. Give the second task a name and then edit the print statements so you know which task is printing. Also add one more print statement after the first task and make the last print statement look a little different. And now run this. Your main actor messages might appear in different places but your named task doesn't begin until the unnamed task finishes. The playground uses at most two threads and one is reserved for the main actor. So you have only one thread for running tasks. In episode four, you'll use the name of the named task to cancel it. So far you've just moved a slow operation off the main actor but you haven't explicitly used the new keywords async and await. Coming right up. Click through to the next page. Scroll down to the section titled Start here. You might have seen the sleep function before, it works like this. Run this and after one second the print message appears. This sleep function is synchronous. It blocks its thread. In this case, it blocks the main thread. So the print message doesn't appear until sleep one finishes, but there's a non-blocking Task.sleep method. Above the sleep code, add this code. I'm using Xcode 14, which has this new Task.sleep method, so I can use seconds instead of nanoseconds. Here are some errors. Task.sleep is an asynchronous function and it can throw a cancellation error. You can't run an asynchronous function on the main thread. You must send it to a background thread. Wrap all three lines in a task. It can throw errors, so you must use try. And this error message means you must use the await keyword when you call an async function. Go ahead and click Fix. Await gives the runtime a suspension point. Place to pause this task, consider if there are other tasks to run on this thread and then resume running this tasks. You always use the keywords try and await in this order. First, you'll await the result of the asynchronous operation and then you check the result for errors. Now run this. Hello appears, and one second later, Goodbye appears. Did you notice something wonderful? Task.sleep is an asynchronous function. In the old concurrency model, you would expect to write some completion code in an escaping closure where you would have to capture self or some other variable. You pretty much don't have to do this when you use the new async-await syntax. When you define an asynchronous function that can throw errors, you reverse the order of the keywords. Scroll up to the section titled Asynchronous functions and create a function. Copy-paste the code in the Task.sleep into this function and edit the print messages. And below this async throwing function create a task to call it. Again, first, you await the completion of the async method, which could throw errors. So then you check for thrown errors. When you run this, it works the same as before. Next, you'll work with a more interesting asynchronous function. Scroll up to the section titled URLSession. You'll download and decode a list of learning domains from Downloading is always an asynchronous operation. You've probably used URLSession.dataTask with completion handler to do this. Open the navigator panel and look at Part2, Sources, Domains.swift. These are the decodable structures that mirror the nested JSON in the server response. Your goal is an array of domain values. Each domain has an attributes property, and these are the attributes you'll print. Select the Part2 page and close the navigator panel. Add a function to fetch the domains. And add a dummy return so the compiler doesn't complain. Now, before the return statement, fetch the data. Data from is the one you want. It's grayed out because the compiler doesn't think it should be used here. Select it anyway. Xcode flags an error. Async call in a function that does not support concurrency because this data from method is a built-in async method. To call it from fetchDomains, you need to tell the that fetchDomains is also an async method. Xcode suggests the correct fix, add async to function fetchDomains. So click Fix. Xcode also complains errors thrown from here are not handled because data from has an advantage over the old data task completion handler. It can throw network errors. So add throws to the function signature. As you saw with helloPauseGoodbye, the keyword async always comes before the keyword throws. And when you call a throwing async function, try comes before await. Now look more closely at this interesting line of code. This URLSession async method returns a data instance and a URL response object, just like the old data task method. You won't use the response now, so it's just this _. Instead of passing a completion handler, calling data from suspense execution in fetchDomains until the data and response arrive from the server. Normally you would first verify the server response but skip that and just decode the data. Replace the dummy return line. This is pretty much what you would write in a data task completion handler, except you would have to handle any decoder errors right away. Now to see if this works. FetchDomains is an async function so you need to call it in a task. It can throw errors. So you need a do catch. FetchDomains returns an array, so loop over it to print out the domains. And now run this task. So that's how Swift concurrency works in a playground. In the next episode you'll start using all this in an actual app.