Modern Concurrency: Getting Started

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

Part 2: Asynchronous Sequences

09. Your Second Asynchronous App

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 08. Introduction Next episode: 10. Concurrency With async let

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.

Transcript: 09. Your Second Asynchronous App

For this part of the course, you’ll work on this cloud file storage app. Your first job is to download a list of files from the server and display it in this view. It’s very similar to how you downloaded and displayed the list of stock symbols for LittleJohn.

So you’ll complete this job as a challenge, to reinforce what you learned in Part 1. See how much you can do without peeking at LittleJohn.

Most of the projects in this course interact with a server, which is included in the course materials. If you skipped Part 1 of this course, start the server by following the instructions at the beginning of episode 3.

If you’re continuing from Part 1 of this course, refresh your browser to make sure the course server is running or restart the server in Terminal.

If you leave this server running, it might stop serving data correctly. If the app doesn’t work as you expect, stop the course server then restart it. In the course materials, locate the SuperStorage starter project and open it.

Your challenge is to download the list of files from the server, then display this list in ListView.

Finish writing the availableFiles() method in Model/SuperStorageModel. You’ll decode the JSON data as an array of DownloadFiles. And add a task view modifier in ListView to call availableFiles() and store its returned array in files

Good luck!

Welcome back! Hopefully you had success with this task. Here’s how I did it.

Non-async code in starter

In SuperStorageModel, here’s availableFiles():

func availableFiles() async throws -> [DownloadFile] {
  guard let url = URL(string: "http://localhost:8080/files/list") else {
    throw "Could not create the URL."
  }
  return []

This looks very similar to availableSymbols in LittleJohnModel. The endpoint is different, and it returns an array of DownloadFiles.

The method’s signature includes async throws to tell the compiler and the Swift runtime how you plan to use it:

  • async says this method is asynchronous: The runtime can suspend its execution until it returns a result.
  • The compiler makes sure you don’t call this method from synchronous contexts that don’t allow the method to suspend and resume the task. Remember in LittleJohn, you had to call availableSymbols from a task view modifier, because SwiftUI views are synchronous.

Add async call: async throws / try await

Now, to fetch the data, I added this line, before the return statement:

let (data, response) = try await URLSession.shared.data(from: url)
🟥return []

This is exactly the same as the data(from:) call in LittleJohn.

Calling the URLSession async method with the await keyword tells the runtime it can suspend this method’s code until data(from:) returns a response. The system can use the thread to do other work.

Handle data, response

Next, to check the server response and return the fetched data, I replaced the dummy return statement:

let (data, response) = try await URLSession.shared.data(from: url)
🟩
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
  throw "The server responded with an error."
}

// Just for a change, I'll throw my own error if `decode` fails...
guard let list = try? JSONDecoder()
  .decode([DownloadFile].self, from: data) else {
  throw "The server response was not recognized."
}
return list

So this method either returns an array or it throws an error.

Call async method from SwiftUI view

Next, I updated the SwiftUI view to use this new async method.

In ListView, I added code where the TODO is, just below .alert(...):

// TODO: Call model.availableFiles()
🟩
.task {
  guard files.isEmpty else { return }  // first check if I already fetched the file list
  do {
    files = try await model.availableFiles()  // if not, call `availableFiles()`
  } catch {
    lastErrorMessage = error.localizedDescription
  }
}

And catch and store any errors in lastErrorMessage. The app will then display the error message in an alert box.

This is exactly like the code in LittleJohn’s SymbolListView, but with files instead of symbols.

As you learned in LittleJohn, task is a view modifier that allows you to execute asynchronous code when the view appears. It also handles canceling the asynchronous execution when the view disappears.

To test, stop the course server, then build and run

No surprise, I get the error “Could not connect to the server”. The URLSession asynchronous method threw this error, and it propagated up the task hierarchy to the task in ListView.

Throwing errors is a key advantage of Swift concurrency. It provides a consistent, robust way to handle errors.

I restart the course server, then build and run again. And there’s the list of files.

The files are TIFF and JPEG images. To save space on your Mac, they’re all the same image: The server creates TIFF and JPEG representations of a gradient image.

That takes care of the list view. Next up: You’ll learn how to download the server status at the same time as the list.